From 966182bd2f32f5dd0d38d98fc96a535e9a16a425 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 29 Dec 2025 10:05:51 +0400 Subject: [PATCH] Various improvements --- submodules/BrowserUI/BUILD | 6 + .../Sources/BrowserAddressBarComponent.swift | 297 +++++---- .../Sources/BrowserAddressListComponent.swift | 28 +- .../BrowserAddressListItemComponent.swift | 13 +- .../Sources/BrowserBookmarksScreen.swift | 140 ++-- .../Sources/BrowserInstantPageContent.swift | 22 +- .../BrowserNavigationBarComponent.swift | 622 +++++++++--------- .../BrowserUI/Sources/BrowserPdfContent.swift | 30 +- .../BrowserUI/Sources/BrowserScreen.swift | 142 ++-- .../Sources/BrowserSearchBarComponent.swift | 313 +-------- .../Sources/BrowserTitleBarComponent.swift | 42 +- .../Sources/BrowserToolbarComponent.swift | 204 +++--- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api15.swift | 18 +- submodules/TelegramApi/Sources/Api39.swift | 18 + .../Sources/Account/AccountManager.swift | 1 + .../ApiUtils/StoreMessage_Telegram.swift | 12 +- .../Sources/State/ApplyUpdateMessage.swift | 4 +- .../Sources/State/PendingMessageManager.swift | 4 +- .../Sources/State/UpdateMessageService.swift | 4 +- .../Sources/State/UpdatesApiUtils.swift | 12 +- .../SummarizationMessageAttribute.swift | 41 ++ .../TelegramEngine/Messages/Summarize.swift | 71 ++ .../Messages/TelegramEngineMessages.swift | 4 + .../CameraScreen/Sources/ModeComponent.swift | 2 +- .../Sources/ChatMessageBubbleItemNode.swift | 50 +- .../ChatMessagePaymentAlertController.swift | 2 +- .../Sources/ChatMessageReplyInfoNode.swift | 125 +++- .../Chat/ChatMessageShareButton/BUILD | 1 + .../Sources/ChatMessageShareButton.swift | 99 ++- .../ChatMessageTextBubbleContentNode.swift | 25 +- .../Sources/GlassBarButtonComponent.swift | 20 +- .../Sources/SearchInputPanelComponent.swift | 4 + .../Sources/ChatbotSetupScreen.swift | 2 +- .../Instant View/Back.imageset/Contents.json | 2 +- .../Back.imageset/browser_back_30.pdf | Bin 0 -> 4189 bytes .../Instant View/Back.imageset/ic_left.pdf | Bin 3911 -> 0 bytes .../Bookmark.imageset/Bookmark.pdf | Bin 2667 -> 0 bytes .../Bookmark.imageset/Contents.json | 2 +- .../Bookmark.imageset/browser_bookmark_30.pdf | Bin 0 -> 5104 bytes .../Instant View/Browser.imageset/Browser.pdf | Bin 2160 -> 0 bytes .../Browser.imageset/Contents.json | 2 +- .../Browser.imageset/browser_safari_30.pdf | Bin 0 -> 4956 bytes .../Forward.imageset/Contents.json | 2 +- .../Forward.imageset/browser_forward_30.pdf | Bin 0 -> 4194 bytes .../Forward.imageset/ic_right.pdf | Bin 3914 -> 0 bytes .../OpenDocument.imageset/Contents.json | 2 +- .../OpenDocument.imageset/browser_doc_30.pdf | Bin 0 -> 5672 bytes .../OpenDocument.imageset/docviewer_24.pdf | Bin 3269 -> 0 bytes .../Search.imageset/Contents.json | 12 + .../Search.imageset/browser_search_30.pdf | Bin 0 -> 4516 bytes .../Instant View/Share.imageset/Contents.json | 12 + .../Share.imageset/browser_share_30.pdf | Bin 0 -> 5208 bytes 53 files changed, 1379 insertions(+), 1035 deletions(-) create mode 100644 submodules/TelegramCore/Sources/SyncCore/SummarizationMessageAttribute.swift create mode 100644 submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/browser_back_30.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Bookmark.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/browser_bookmark_30.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Browser.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/browser_safari_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/browser_forward_30.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/browser_doc_30.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/docviewer_24.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/browser_search_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/browser_share_30.pdf diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index bf7844ea91..472c755041 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -51,6 +51,12 @@ swift_library( "//submodules/Utils/DeviceModel", "//submodules/LegacyMediaPickerUI", "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/SearchInputPanelComponent", + "//submodules/TelegramUI/Components/GlassControls", ], visibility = [ "//visibility:public", diff --git a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift index da83dddb91..00915444a4 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift @@ -9,6 +9,9 @@ import AccountContext import BundleIconComponent import MultilineTextComponent import UrlEscaping +import GlassBackgroundComponent +import GlassBarButtonComponent +import EdgeEffect final class AddressBarContentComponent: Component { public typealias EnvironmentType = BrowserNavigationBarEnvironment @@ -19,6 +22,8 @@ final class AddressBarContentComponent: Component { let url: String let isSecure: Bool let isExpanded: Bool + let readingProgress: CGFloat + let loadingProgress: Double? let performAction: ActionSlot init( @@ -28,6 +33,8 @@ final class AddressBarContentComponent: Component { url: String, isSecure: Bool, isExpanded: Bool, + readingProgress: CGFloat, + loadingProgress: Double?, performAction: ActionSlot ) { self.theme = theme @@ -36,6 +43,8 @@ final class AddressBarContentComponent: Component { self.url = url self.isSecure = isSecure self.isExpanded = isExpanded + self.readingProgress = readingProgress + self.loadingProgress = loadingProgress self.performAction = performAction } @@ -58,6 +67,12 @@ final class AddressBarContentComponent: Component { if lhs.isExpanded != rhs.isExpanded { return false } + if lhs.readingProgress != rhs.readingProgress { + return false + } + if lhs.loadingProgress != rhs.loadingProgress { + return false + } return true } @@ -85,6 +100,8 @@ final class AddressBarContentComponent: Component { var isSecure: Bool var collapseFraction: CGFloat var isTablet: Bool + var readingProgress: CGFloat + var loadingProgress: Double? static func ==(lhs: Params, rhs: Params) -> Bool { if lhs.theme !== rhs.theme { @@ -111,26 +128,33 @@ final class AddressBarContentComponent: Component { if lhs.isTablet != rhs.isTablet { return false } + if lhs.readingProgress != rhs.readingProgress { + return false + } + if lhs.loadingProgress != rhs.loadingProgress { + return false + } return true } } private let activated: (Bool) -> Void = { _ in } private let deactivated: (Bool) -> Void = { _ in } - - private let backgroundLayer: SimpleLayer - - private let iconView: UIImageView + + private let backgroundView: GlassBackgroundView private let clearIconView: UIImageView private let clearIconButton: HighlightTrackingButton - private let cancelButtonTitle: ComponentView - private let cancelButton: HighlightTrackingButton + private let cancelButton = ComponentView() private var placeholderContent = ComponentView() private var titleContent = ComponentView() + private let clippingView = UIView() + private var loadingProgress = ComponentView() + private var readingProgressView = UIView() + private var textFrame: CGRect? private var textField: TextField? @@ -144,50 +168,30 @@ final class AddressBarContentComponent: Component { } init() { - self.backgroundLayer = SimpleLayer() - - self.iconView = UIImageView() + self.backgroundView = GlassBackgroundView() self.clearIconView = UIImageView() self.clearIconButton = HighlightableButton() self.clearIconView.isHidden = false self.clearIconButton.isHidden = false - - self.cancelButtonTitle = ComponentView() - self.cancelButton = HighlightTrackingButton() - + super.init(frame: CGRect()) - self.layer.addSublayer(self.backgroundLayer) + self.clippingView.clipsToBounds = true - self.addSubview(self.iconView) - self.addSubview(self.clearIconView) - self.addSubview(self.clearIconButton) + self.addSubview(self.backgroundView) + self.backgroundView.contentView.addSubview(self.clippingView) + self.clippingView.addSubview(self.readingProgressView) + + self.backgroundView.contentView.addSubview(self.clearIconView) + self.backgroundView.contentView.addSubview(self.clearIconButton) - self.addSubview(self.cancelButton) self.clipsToBounds = true let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) self.tapRecognizer = tapRecognizer self.addGestureRecognizer(tapRecognizer) - - self.cancelButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.layer.removeAnimation(forKey: "opacity") - cancelButtonTitleView.alpha = 0.4 - } - } else { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.alpha = 1.0 - cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside) - + self.clearIconButton.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { @@ -263,11 +267,16 @@ final class AddressBarContentComponent: Component { self.placeholderContent.view?.isHidden = !text.isEmpty if let params = self.params { - self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, isTablet: params.isTablet, transition: .immediate) + self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, isTablet: params.isTablet, readingProgress: params.readingProgress, loadingProgress: params.loadingProgress, transition: .immediate) } } - func update(component: AddressBarContentComponent, availableSize: CGSize, environment: Environment, transition: ComponentTransition) -> CGSize { + func update( + component: AddressBarContentComponent, + availableSize: CGSize, + environment: Environment, + transition: ComponentTransition + ) -> CGSize { let collapseFraction = environment[BrowserNavigationBarEnvironment.self].fraction let wasExpanded = self.component?.isExpanded ?? false @@ -282,12 +291,36 @@ final class AddressBarContentComponent: Component { let isActive = self.textField?.isFirstResponder ?? false let title = getDisplayUrl(component.url, hostOnly: true) - self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, isTablet: component.metrics.isTablet, transition: transition) + self.update( + theme: component.theme, + strings: component.strings, + size: availableSize, + isActive: isActive, + title: title.lowercased(), + isSecure: component.isSecure, + collapseFraction: collapseFraction, + isTablet: component.metrics.isTablet, + readingProgress: component.readingProgress, + loadingProgress: component.loadingProgress, + transition: transition + ) return availableSize } - public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, isActive: Bool, title: String, isSecure: Bool, collapseFraction: CGFloat, isTablet: Bool, transition: ComponentTransition) { + public func update( + theme: PresentationTheme, + strings: PresentationStrings, + size: CGSize, + isActive: Bool, + title: String, + isSecure: Bool, + collapseFraction: CGFloat, + isTablet: Bool, + readingProgress: CGFloat, + loadingProgress: Double?, + transition: ComponentTransition + ) { let params = Params( theme: theme, strings: strings, @@ -296,7 +329,9 @@ final class AddressBarContentComponent: Component { title: title, isSecure: isSecure, collapseFraction: collapseFraction, - isTablet: isTablet + isTablet: isTablet, + readingProgress: readingProgress, + loadingProgress: loadingProgress ) if self.params == params { @@ -306,8 +341,6 @@ final class AddressBarContentComponent: Component { let isActiveWithText = self.component?.isExpanded ?? false if self.params?.theme !== theme { - self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Lock"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.iconView.tintColor = theme.rootController.navigationSearchBar.inputIconColor self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate) self.clearIconView.tintColor = theme.rootController.navigationSearchBar.inputClearButtonColor } @@ -315,57 +348,9 @@ final class AddressBarContentComponent: Component { self.params = params let sideInset: CGFloat = 10.0 - let inputHeight: CGFloat = 36.0 + let inputHeight: CGFloat = 44.0 let topInset: CGFloat = (size.height - inputHeight) / 2.0 - self.backgroundLayer.backgroundColor = theme.rootController.navigationSearchBar.inputFillColor.cgColor - self.backgroundLayer.cornerRadius = 10.5 - transition.setAlpha(layer: self.backgroundLayer, alpha: max(0.0, min(1.0, 1.0 - collapseFraction * 1.5))) - - let cancelTextSize = self.cancelButtonTitle.update( - transition: .immediate, - component: AnyComponent(Text( - text: strings.Common_Cancel, - font: Font.regular(17.0), - color: theme.rootController.navigationBar.accentTextColor - )), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: 100.0) - ) - - let cancelButtonSpacing: CGFloat = 8.0 - - var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight)) - if isActiveWithText && !isTablet { - backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing - } - transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) - - transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) - self.cancelButton.isUserInteractionEnabled = isActiveWithText && !isTablet - - let textX: CGFloat = backgroundFrame.minX + sideInset - let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) - - let placeholderSize = self.placeholderContent.update( - transition: transition, - component: AnyComponent( - Text(text: strings.WebBrowser_AddressPlaceholder, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) - ), - environment: {}, - containerSize: size - ) - if let placeholderContentView = self.placeholderContent.view { - if placeholderContentView.superview == nil { - placeholderContentView.alpha = 0.0 - placeholderContentView.isHidden = true - self.addSubview(placeholderContentView) - } - let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.midY - placeholderSize.height / 2.0), size: placeholderSize) - transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) - transition.setAlpha(view: placeholderContentView, alpha: isActiveWithText ? 1.0 : 0.0) - } - let titleSize = self.titleContent.update( transition: transition, component: AnyComponent( @@ -379,51 +364,96 @@ final class AddressBarContentComponent: Component { environment: {}, containerSize: CGSize(width: size.width - 36.0, height: size.height) ) - var titleContentFrame = CGRect(origin: CGPoint(x: isActiveWithText ? textFrame.minX : backgroundFrame.midX - titleSize.width / 2.0, y: backgroundFrame.midY - titleSize.height / 2.0), size: titleSize) - if isSecure && !isActiveWithText { - titleContentFrame.origin.x += 7.0 + + let expandedBackgroundWidth = size.width - 14.0 * 2.0 + let collapsedBackgroundWidth = titleSize.width + 32.0 + var backgroundSize = CGSize(width: expandedBackgroundWidth * (1.0 - collapseFraction) + collapsedBackgroundWidth * collapseFraction, height: 44.0) + + let cancelButtonSpacing: CGFloat = 8.0 + let cancelSize = self.cancelButton.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent(name: "Navigation/Close", tintColor: theme.chat.inputPanel.panelControlColor) + )), + action: { [weak self] _ in + self?.cancelPressed() + } + ) + ), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + + if isActiveWithText && !isTablet { + backgroundSize.width -= cancelSize.width + cancelButtonSpacing + 4.0 } - var titleSizeChanged = false + self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + + var backgroundFrame = CGRect(origin: CGPoint(x: floor((size.width - backgroundSize.width) / 2.0), y: topInset), size: backgroundSize) + if isActiveWithText && !isTablet { + backgroundFrame.origin.x = 16.0 + } + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + + transition.setFrame(view: self.clippingView, frame: CGRect(origin: .zero, size: backgroundFrame.size)) + transition.setCornerRadius(layer: self.clippingView.layer, cornerRadius: backgroundFrame.size.height * 0.5) + + let textX: CGFloat = sideInset + let textFrame = CGRect(origin: CGPoint(x: textX, y: 0.0), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) + + let placeholderSize = self.placeholderContent.update( + transition: transition, + component: AnyComponent( + Text(text: strings.WebBrowser_AddressPlaceholder, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + ), + environment: {}, + containerSize: size + ) + if let placeholderContentView = self.placeholderContent.view { + if placeholderContentView.superview == nil { + placeholderContentView.alpha = 0.0 + placeholderContentView.isHidden = true + self.backgroundView.contentView.addSubview(placeholderContentView) + } + let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.size.height / 2.0 - placeholderSize.height / 2.0), size: placeholderSize) + transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) + transition.setAlpha(view: placeholderContentView, alpha: isActiveWithText ? 1.0 : 0.0) + } + + let titleContentFrame = CGRect(origin: CGPoint(x: isActiveWithText ? textFrame.minX : backgroundFrame.width / 2.0 - titleSize.width / 2.0, y: backgroundFrame.height / 2.0 - titleSize.height / 2.0), size: titleSize) if let titleContentView = self.titleContent.view { if titleContentView.superview == nil { - self.addSubview(titleContentView) - } - if titleContentView.frame.width != titleContentFrame.size.width { - titleSizeChanged = true + self.backgroundView.contentView.addSubview(titleContentView) } transition.setPosition(view: titleContentView, position: titleContentFrame.center) titleContentView.bounds = CGRect(origin: .zero, size: titleContentFrame.size) transition.setAlpha(view: titleContentView, alpha: isActiveWithText ? 0.0 : 1.0) } - - if let image = self.iconView.image { - let iconFrame = CGRect(origin: CGPoint(x: titleContentFrame.minX - image.size.width - 3.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - var iconTransition = transition - if titleSizeChanged { - iconTransition = .immediate - } - iconTransition.setFrame(view: self.iconView, frame: iconFrame) - transition.setAlpha(view: self.iconView, alpha: isActiveWithText || !isSecure ? 0.0 : 1.0) - } - + if let image = self.clearIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) + let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.width - image.size.width - 4.0, y: floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) transition.setFrame(view: self.clearIconView, frame: iconFrame) transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0)) transition.setAlpha(view: self.clearIconView, alpha: isActiveWithText ? 1.0 : 0.0) self.clearIconButton.isUserInteractionEnabled = isActiveWithText } - if let cancelButtonTitleComponentView = self.cancelButtonTitle.view { - if cancelButtonTitleComponentView.superview == nil { - self.addSubview(cancelButtonTitleComponentView) - cancelButtonTitleComponentView.isUserInteractionEnabled = false + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) } - transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) - transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText && !isTablet ? 1.0 : 0.0) + transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelSize.height) / 2.0)), size: cancelSize)) + transition.setAlpha(view: cancelButtonView, alpha: isActiveWithText && !isTablet ? 1.0 : 0.0) } - let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height)) + let textFieldFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: CGSize(width: backgroundFrame.width - sideInset, height: backgroundFrame.height)) let textField: TextField if let current = self.textField { @@ -434,7 +464,7 @@ final class AddressBarContentComponent: Component { textField.autocorrectionType = .no textField.keyboardType = .URL textField.returnKeyType = .go - self.insertSubview(textField, belowSubview: self.clearIconView) + self.backgroundView.contentView.insertSubview(textField, belowSubview: self.clearIconView) self.textField = textField textField.delegate = self @@ -450,9 +480,34 @@ final class AddressBarContentComponent: Component { } textField.textColor = theme.rootController.navigationSearchBar.inputTextColor - transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height))) + transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: sideInset, y: -UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height))) transition.setAlpha(view: textField, alpha: isActiveWithText ? 1.0 : 0.0) textField.isUserInteractionEnabled = isActiveWithText + + + let loadingProgressInset: CGFloat = 12.0 + let loadingProgressSize = CGSize(width: backgroundSize.width - loadingProgressInset * 2.0, height: 2.0) + let _ = self.loadingProgress.update( + transition: transition, + component: AnyComponent(LoadingProgressComponent( + color: theme.rootController.navigationBar.accentTextColor, + height: loadingProgressSize.height, + value: params.loadingProgress ?? 0.0 + )), + environment: {}, + containerSize: loadingProgressSize + ) + if let loadingProgressView = self.loadingProgress.view { + if loadingProgressView.superview == nil { + self.clippingView.addSubview(loadingProgressView) + } + transition.setFrame(view: loadingProgressView, frame: CGRect(origin: CGPoint(x: loadingProgressInset, y: backgroundSize.height - loadingProgressSize.height), size: loadingProgressSize)) + transition.setAlpha(view: loadingProgressView, alpha: isActiveWithText ? 0.0 : 1.0) + } + + self.readingProgressView.backgroundColor = theme.rootController.navigationBar.primaryTextColor.withMultipliedAlpha(0.07) + self.readingProgressView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundSize.width * params.readingProgress, height: backgroundSize.height)) + transition.setAlpha(view: self.readingProgressView, alpha: isActiveWithText ? 0.0 : 1.0) } } diff --git a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift index 1551fcf407..cd308ad441 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift @@ -19,6 +19,7 @@ final class BrowserAddressListComponent: Component { let insets: UIEdgeInsets let metrics: LayoutMetrics let addressBarFrame: CGRect + let navigationBarHeight: CGFloat let performAction: ActionSlot let presentInGlobalOverlay: (ViewController) -> Void @@ -29,6 +30,7 @@ final class BrowserAddressListComponent: Component { insets: UIEdgeInsets, metrics: LayoutMetrics, addressBarFrame: CGRect, + navigationBarHeight: CGFloat, performAction: ActionSlot, presentInGlobalOverlay: @escaping (ViewController) -> Void ) { @@ -38,6 +40,7 @@ final class BrowserAddressListComponent: Component { self.insets = insets self.metrics = metrics self.addressBarFrame = addressBarFrame + self.navigationBarHeight = navigationBarHeight self.performAction = performAction self.presentInGlobalOverlay = presentInGlobalOverlay } @@ -61,6 +64,9 @@ final class BrowserAddressListComponent: Component { if lhs.addressBarFrame != rhs.addressBarFrame { return false } + if lhs.navigationBarHeight != rhs.navigationBarHeight { + return false + } return true } @@ -214,15 +220,16 @@ final class BrowserAddressListComponent: Component { var validIds: [AnyHashable] = [] var validSectionHeaders: [AnyHashable] = [] var sectionOffset: CGFloat = 0.0 + let headerOffset: CGFloat = self.scrollView.frame.minY let sideInset: CGFloat = 0.0 - let containerInset: CGFloat = 0.0 + let containerInset: CGFloat = self.scrollView.frame.minY for sectionIndex in 0 ..< itemLayout.sections.count { let section = itemLayout.sections[sectionIndex] do { - var sectionHeaderFrame = CGRect(origin: CGPoint(x: sideInset, y: sectionOffset - self.scrollView.bounds.minY), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top)) + var sectionHeaderFrame = CGRect(origin: CGPoint(x: sideInset, y: headerOffset + sectionOffset - self.scrollView.bounds.minY), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top)) let sectionHeaderMinY = topOffset + containerInset let sectionHeaderMaxY = containerInset + sectionOffset - self.scrollView.bounds.minY + section.totalHeight - 28.0 @@ -540,7 +547,7 @@ final class BrowserAddressListComponent: Component { let containerFrame: CGRect if component.metrics.isTablet { let containerSize = CGSize(width: component.addressBarFrame.width + 32.0, height: 540.0) - containerFrame = CGRect(origin: CGPoint(x: floor(component.addressBarFrame.center.x - containerSize.width / 2.0), y: 72.0), size: containerSize) + containerFrame = CGRect(origin: CGPoint(x: floor(component.addressBarFrame.center.x - containerSize.width / 2.0), y: 100.0), size: containerSize) self.backgroundView.layer.cornerRadius = 10.0 } else { @@ -602,23 +609,28 @@ final class BrowserAddressListComponent: Component { let itemLayout = ItemLayout(containerSize: containerFrame.size, insets: .zero, sections: sections) self.itemLayout = itemLayout - let containerWidth = containerFrame.size.width let scrollContentHeight = max(itemLayout.contentHeight, containerFrame.size.height) self.ignoreScrolling = true - transition.setFrame(view: self.scrollView, frame: CGRect(origin: .zero, size: containerFrame.size)) - let contentSize = CGSize(width: containerWidth, height: scrollContentHeight) + let scrollFrame: CGRect + if component.metrics.isTablet { + scrollFrame = CGRect(origin: .zero, size: containerFrame.size) + } else { + scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: component.navigationBarHeight), size: CGSize(width: containerFrame.size.width, height: containerFrame.size.height - component.navigationBarHeight)) + } + transition.setFrame(view: self.scrollView, frame: scrollFrame) + let contentSize = CGSize(width: scrollFrame.width, height: scrollContentHeight) if contentSize != self.scrollView.contentSize { self.scrollView.contentSize = contentSize } if resetScrolling { - self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: containerWidth, height: containerFrame.size.height)) + self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollFrame.size) } self.ignoreScrolling = false self.updateScrolling(transition: transition) transition.setFrame(view: self.backgroundView, frame: containerFrame) - transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: .zero, size: CGSize(width: containerWidth, height: scrollContentHeight))) + transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: .zero, size: CGSize(width: scrollFrame.width, height: scrollContentHeight))) if component.metrics.isTablet { transition.setFrame(view: self.shadowView, frame: containerFrame.insetBy(dx: -60.0, dy: -60.0)) diff --git a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift index f41a5395ea..49c9ece448 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift @@ -12,8 +12,15 @@ import AccountContext import ContextUI import UrlEscaping -private let iconFont = Font.with(size: 30.0, design: .round, weight: .bold) -private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor(rgb: 0xFF9500)) +private let iconFont = Font.with(size: 28.0, design: .round, weight: .bold) +private let iconTextBackgroundImage = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor(rgb: 0xFF9500).cgColor) + + let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 12.0) + context.addPath(path.cgPath) + context.fillPath() +}) final class BrowserAddressListItemComponent: Component { let context: AccountContext @@ -261,7 +268,7 @@ final class BrowserAddressListItemComponent: Component { let iconImageLayout = self.icon.asyncLayout() var iconImageApply: (() -> Void)? if let iconImageReferenceAndRepresentation = iconImageReferenceAndRepresentation { - let imageCorners = ImageCorners(radius: 6.0) + let imageCorners = ImageCorners(radius: 12.0, curve: .continuous) let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageReferenceAndRepresentation.1.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) } diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index 2a4cc84d13..d5fa9632b5 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -18,6 +18,10 @@ import SearchBarNode import ChatHistorySearchContainerNode import ContextUI import UndoUI +import ComponentFlow +import EdgeEffect +import ButtonComponent +import BundleIconComponent public final class BrowserBookmarksScreen: ViewController { final class Node: ViewControllerTracingNode, ASScrollViewDelegate { @@ -479,10 +483,8 @@ private class BottomPanelNode: ASDisplayNode { private let strings: PresentationStrings private let action: () -> Void - private let separatorNode: ASDisplayNode - private let button: HighlightTrackingButtonNode - private let iconNode: ASImageNode - private let textNode: ImmediateTextNode + private let edgeEffectView = EdgeEffectView() + private let button = ComponentView() private var validLayout: (CGFloat, CGFloat, CGFloat)? @@ -491,49 +493,13 @@ private class BottomPanelNode: ASDisplayNode { self.strings = strings self.action = action - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor - - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddIcon"), color: theme.rootController.navigationBar.accentTextColor) - self.iconNode.isUserInteractionEnabled = false - - self.textNode = ImmediateTextNode() - self.textNode.displaysAsynchronously = false - self.textNode.attributedText = NSAttributedString(string: strings.WebBrowser_Bookmarks_BookmarkCurrent, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor) - self.textNode.isUserInteractionEnabled = false - - self.button = HighlightTrackingButtonNode() - super.init() + } + + override func didLoad() { + super.didLoad() - self.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor - - self.addSubnode(self.button) - self.addSubnode(self.separatorNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.textNode) - self.addSubnode(self.button) - - self.button.highligthedChanged = { [weak self] highlighted in - if let self { - if highlighted { - self.iconNode.layer.removeAnimation(forKey: "opacity") - self.iconNode.alpha = 0.4 - - self.textNode.layer.removeAnimation(forKey: "opacity") - self.textNode.alpha = 0.4 - } else { - self.iconNode.alpha = 1.0 - self.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - - self.textNode.alpha = 1.0 - self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.view.addSubview(self.edgeEffectView) } @objc private func buttonPressed() { @@ -546,26 +512,76 @@ private class BottomPanelNode: ASDisplayNode { var bottomInset = bottomInset bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0) - let buttonHeight: CGFloat = 40.0 - let textSize = self.textNode.updateLayout(CGSize(width: width, height: 44.0)) +// let buttonHeight: CGFloat = 40.0 +// let textSize = self.textNode.updateLayout(CGSize(width: width, height: 44.0)) +// +// let spacing: CGFloat = 8.0 +// var contentWidth = textSize.width +// var contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0) +// if let icon = self.iconNode.image { +// contentWidth += icon.size.width + spacing +// contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0) +// transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: contentOriginX, y: 12.0 + UIScreenPixel), size: icon.size)) +// contentOriginX += icon.size.width + spacing +// } +// let textFrame = CGRect(origin: CGPoint(x: contentOriginX, y: 17.0), size: textSize) +// transition.updateFrame(node: self.textNode, frame: textFrame) +// +// transition.updateFrame(node: self.button, frame: textFrame.insetBy(dx: -10.0, dy: -10.0)) +// +// transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) +// + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: bottomInset, innerDiameter: 52.0, sideInset: 30.0) + let height: CGFloat = 52.0 + buttonInsets.bottom - let spacing: CGFloat = 8.0 - var contentWidth = textSize.width - var contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0) - if let icon = self.iconNode.image { - contentWidth += icon.size.width + spacing - contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0) - transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: contentOriginX, y: 12.0 + UIScreenPixel), size: icon.size)) - contentOriginX += icon.size.width + spacing + let edgeEffectHeight: CGFloat = height + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: height - edgeEffectHeight), size: CGSize(width: width, height: edgeEffectHeight)) + transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update( + content: self.theme.list.plainBackgroundColor, + blur: true, + rect: edgeEffectFrame, + edge: .bottom, + edgeSize: edgeEffectFrame.height, + transition: ComponentTransition(transition) + ) + + var buttonItems: [AnyComponentWithIdentity] = [] + buttonItems.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Fave", tintColor: self.theme.list.itemCheckColors.foregroundColor)))) + buttonItems.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: self.strings.WebBrowser_Bookmarks_BookmarkCurrent, font: Font.semibold(17.0), color: self.theme.list.itemCheckColors.foregroundColor)))) + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent( + ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: self.theme.list.itemCheckColors.fillColor, + foreground: self.theme.list.itemCheckColors.foregroundColor, + pressedColor: self.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack(buttonItems, spacing: 7.0)) + ), + action: { [weak self] in + self?.action() + } + ) + ), + environment: {}, + containerSize: CGSize(width: width - sideInset * 2.0 - buttonInsets.left - buttonInsets.right, height: 52.0) + ) + let buttonFrame = CGRect(origin: CGPoint(x: sideInset + buttonInsets.left, y: height - buttonInsets.bottom - buttonSize.height), size: buttonSize) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.view.addSubview(buttonView) + } + transition.updateFrame(view: buttonView, frame: buttonFrame) } - let textFrame = CGRect(origin: CGPoint(x: contentOriginX, y: 17.0), size: textSize) - transition.updateFrame(node: self.textNode, frame: textFrame) - transition.updateFrame(node: self.button, frame: textFrame.insetBy(dx: -10.0, dy: -10.0)) - - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) - - return topInset + buttonHeight + bottomInset + return height } } diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index b09b444e2c..cb03ff6edf 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -90,12 +90,21 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg private let updateLayoutDisposable = MetaDisposable() private let loadProgress = ValuePromise(1.0, ignoreRepeated: true) - private let readingProgress = ValuePromise(1.0, ignoreRepeated: true) + private let readingProgress = ValuePromise(0.0, ignoreRepeated: true) private var containerLayout: (size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets)? private var setupScrollOffsetOnLayout = false - init(context: AccountContext, presentationData: PresentationData, webPage: TelegramMediaWebpage, anchor: String?, url: String, sourceLocation: InstantPageSourceLocation, preloadedResouces: [Any]?, originalContent: BrowserContent? = nil) { + init( + context: AccountContext, + presentationData: PresentationData, + webPage: TelegramMediaWebpage, + anchor: String?, + url: String, + sourceLocation: InstantPageSourceLocation, + preloadedResouces: [Any]?, + originalContent: BrowserContent? = nil + ) { self.context = context var instantPage: InstantPage? if case let .Loaded(content) = webPage.content { @@ -132,6 +141,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg super.init(frame: .zero) + self.backgroundColor = self.theme.pageBackgroundColor + self.statePromise.set(.single(self._state) |> then( combineLatest( @@ -193,6 +204,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.presentationData = presentationData self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: self.settings) + self.backgroundColor = self.theme.pageBackgroundColor + self.updatePageLayout() self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) } @@ -415,8 +428,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg var updateVisibleItems = false let resetContentOffset = self.scrollNode.bounds.size.width.isZero || self.setupScrollOffsetOnLayout || !(self.initialAnchor ?? "").isEmpty - var scrollInsets = insets - scrollInsets.top = 0.0 + let scrollInsets = fullInsets if self.scrollNode.view.contentInset != scrollInsets { self.scrollNode.view.contentInset = scrollInsets self.scrollNode.view.scrollIndicatorInsets = scrollInsets @@ -424,7 +436,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.wrapperNode.frame = CGRect(origin: .zero, size: size) - let scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top)) + let scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) let scrollFrameUpdated = self.scrollNode.bounds.size != scrollFrame.size if scrollFrameUpdated { let widthUpdated = self.scrollNode.bounds.size.width != scrollFrame.width diff --git a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift index 89e0387e3d..26cc5ae129 100644 --- a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift @@ -2,8 +2,10 @@ import Foundation import UIKit import Display import ComponentFlow -import BlurredBackgroundComponent +import TelegramPresentationData import ContextUI +import GlassBackgroundComponent +import EdgeEffect final class BrowserNavigationBarEnvironment: Equatable { public let fraction: CGFloat @@ -20,7 +22,7 @@ final class BrowserNavigationBarEnvironment: Equatable { } } -final class BrowserNavigationBarComponent: CombinedComponent { +final class BrowserNavigationBarComponent: Component { public class ExternalState { public fileprivate(set) var centerItemFrame: CGRect @@ -29,11 +31,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { } } - let backgroundColor: UIColor - let separatorColor: UIColor - let textColor: UIColor - let progressColor: UIColor - let accentColor: UIColor + let theme: PresentationTheme let topInset: CGFloat let height: CGFloat let sideInset: CGFloat @@ -42,17 +40,11 @@ final class BrowserNavigationBarComponent: CombinedComponent { let leftItems: [AnyComponentWithIdentity] let rightItems: [AnyComponentWithIdentity] let centerItem: AnyComponentWithIdentity? - let readingProgress: CGFloat - let loadingProgress: Double? let collapseFraction: CGFloat let activate: () -> Void init( - backgroundColor: UIColor, - separatorColor: UIColor, - textColor: UIColor, - progressColor: UIColor, - accentColor: UIColor, + theme: PresentationTheme, topInset: CGFloat, height: CGFloat, sideInset: CGFloat, @@ -61,16 +53,10 @@ final class BrowserNavigationBarComponent: CombinedComponent { leftItems: [AnyComponentWithIdentity], rightItems: [AnyComponentWithIdentity], centerItem: AnyComponentWithIdentity?, - readingProgress: CGFloat, - loadingProgress: Double?, collapseFraction: CGFloat, activate: @escaping () -> Void ) { - self.backgroundColor = backgroundColor - self.separatorColor = separatorColor - self.textColor = textColor - self.progressColor = progressColor - self.accentColor = accentColor + self.theme = theme self.topInset = topInset self.height = height self.sideInset = sideInset @@ -79,26 +65,12 @@ final class BrowserNavigationBarComponent: CombinedComponent { self.leftItems = leftItems self.rightItems = rightItems self.centerItem = centerItem - self.readingProgress = readingProgress - self.loadingProgress = loadingProgress self.collapseFraction = collapseFraction self.activate = activate } static func ==(lhs: BrowserNavigationBarComponent, rhs: BrowserNavigationBarComponent) -> Bool { - if lhs.backgroundColor != rhs.backgroundColor { - return false - } - if lhs.separatorColor != rhs.separatorColor { - return false - } - if lhs.textColor != rhs.textColor { - return false - } - if lhs.progressColor != rhs.progressColor { - return false - } - if lhs.accentColor != rhs.accentColor { + if lhs.theme !== rhs.theme { return false } if lhs.topInset != rhs.topInset { @@ -122,203 +94,353 @@ final class BrowserNavigationBarComponent: CombinedComponent { if lhs.centerItem != rhs.centerItem { return false } - if lhs.readingProgress != rhs.readingProgress { - return false - } - if lhs.loadingProgress != rhs.loadingProgress { - return false - } if lhs.collapseFraction != rhs.collapseFraction { return false } return true } - static var body: Body { - let background = Child(Rectangle.self) - let readingProgress = Child(Rectangle.self) - let separator = Child(Rectangle.self) - let loadingProgress = Child(LoadingProgressComponent.self) - let leftItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let centerItems = ChildMap(environment: BrowserNavigationBarEnvironment.self, keyedBy: AnyHashable.self) - let activate = Child(Button.self) + final class View: UIView { + private var edgeEffectView = EdgeEffectView() + private let containerView = GlassBackgroundContainerView() + + private var leftItemsBackground: GlassBackgroundView? + private var leftItems: [AnyHashable: ComponentView] = [:] - return { context in - var availableWidth = context.availableSize.width - let sideInset: CGFloat = (context.component.metrics.isTablet ? 20.0 : 16.0) + context.component.sideInset + private var rightItemsBackground: GlassBackgroundView? + private var rightItems: [AnyHashable: ComponentView] = [:] + + private var centerItems: [AnyHashable: ComponentView] = [:] + + private let activateButton = HighlightTrackingButton() + + private var component: BrowserNavigationBarComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) - let collapsedHeight: CGFloat = 24.0 - let expandedHeight = context.component.height - let contentHeight: CGFloat = expandedHeight * (1.0 - context.component.collapseFraction) + collapsedHeight * context.component.collapseFraction - let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight) - let verticalOffset: CGFloat = context.component.metrics.isTablet ? -2.0 : 0.0 - let itemSpacing: CGFloat = context.component.metrics.isTablet ? 26.0 : 8.0 + self.addSubview(self.edgeEffectView) - let background = background.update( - component: Rectangle(color: context.component.backgroundColor.withAlphaComponent(1.0)), - availableSize: CGSize(width: size.width, height: size.height), - transition: context.transition - ) - - let readingProgress = readingProgress.update( - component: Rectangle(color: context.component.progressColor), - availableSize: CGSize(width: size.width * context.component.readingProgress, height: size.height), - transition: context.transition - ) - - let separator = separator.update( - component: Rectangle(color: context.component.separatorColor, height: UIScreenPixel), - availableSize: CGSize(width: size.width, height: size.height), - transition: context.transition - ) - - let loadingProgressHeight: CGFloat = 2.0 - let loadingProgress = loadingProgress.update( - component: LoadingProgressComponent( - color: context.component.accentColor, - height: loadingProgressHeight, - value: context.component.loadingProgress ?? 0.0 - ), - availableSize: CGSize(width: size.width, height: size.height), - transition: context.transition - ) - - var leftItemList: [_UpdatedChildComponent] = [] - for item in context.component.leftItems { - let item = leftItems[item.id].update( - component: item.component, - availableSize: CGSize(width: availableWidth, height: expandedHeight), - transition: context.transition - ) - leftItemList.append(item) - availableWidth -= item.size.width - } - - var rightItemList: [_UpdatedChildComponent] = [] - for item in context.component.rightItems { - let item = rightItems[item.id].update( - component: item.component, - availableSize: CGSize(width: availableWidth, height: expandedHeight), - transition: context.transition - ) - rightItemList.append(item) - availableWidth -= item.size.width + self.addSubview(self.containerView) + self.activateButton.addTarget(self, action: #selector(self.activatePressed), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + @objc private func activatePressed() { + guard let component = self.component else { + return } + component.activate() + } + + func update(component: BrowserNavigationBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + var availableWidth = availableSize.width + let sideInset: CGFloat = (component.metrics.isTablet ? 20.0 : 16.0) + component.sideInset + + let collapsedHeight: CGFloat = 54.0 + let expandedHeight = component.height + let contentHeight: CGFloat = expandedHeight * (1.0 - component.collapseFraction) + collapsedHeight * component.collapseFraction + let size = CGSize(width: availableSize.width, height: component.topInset + contentHeight) + let verticalOffset: CGFloat = component.metrics.isTablet ? -2.0 : 0.0 + let itemSpacing: CGFloat = 0.0 //component.metrics.isTablet ? 26.0 : 8.0 + let panelHeight: CGFloat = 44.0 + + var leftItemsBackground: GlassBackgroundView? + var leftItemsBackgroundTransition = transition + if !component.leftItems.isEmpty { + if let current = self.leftItemsBackground { + leftItemsBackground = current + } else { + leftItemsBackgroundTransition = .immediate + leftItemsBackground = GlassBackgroundView() + self.containerView.contentView.addSubview(leftItemsBackground!) + self.leftItemsBackground = leftItemsBackground - context.add(background - .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) - ) - - var readingProgressAlpha = context.component.collapseFraction - if leftItemList.isEmpty && rightItemList.isEmpty { - readingProgressAlpha = 0.0 + transition.animateScale(view: leftItemsBackground!, from: 0.1, to: 1.0) + transition.animateAlpha(view: leftItemsBackground!, from: 0.0, to: 1.0) + } } - context.add(readingProgress - .position(CGPoint(x: readingProgress.size.width / 2.0, y: size.height / 2.0)) - .opacity(readingProgressAlpha) - ) - context.add(separator - .position(CGPoint(x: size.width / 2.0, y: size.height)) - ) - - context.add(loadingProgress - .position(CGPoint(x: size.width / 2.0, y: size.height - loadingProgressHeight / 2.0)) - ) + var rightItemsBackground: GlassBackgroundView? + var rightItemsBackgroundTransition = transition + if !component.rightItems.isEmpty { + if let current = self.rightItemsBackground { + rightItemsBackground = current + } else { + rightItemsBackgroundTransition = .immediate + rightItemsBackground = GlassBackgroundView() + self.containerView.contentView.addSubview(rightItemsBackground!) + self.rightItemsBackground = rightItemsBackground + + transition.animateScale(view: rightItemsBackground!, from: 0.1, to: 1.0) + transition.animateAlpha(view: rightItemsBackground!, from: 0.0, to: 1.0) + } + } + + var validLeftItemIds: Set = Set() + var leftItemTransitions: [AnyHashable: (CGSize, ComponentTransition)] = [:] + var leftItemsWidth: CGFloat = 0.0 + for item in component.leftItems { + validLeftItemIds.insert(item.id) + var itemTransition = transition + let itemView: ComponentView + if let current = self.leftItems[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ComponentView() + self.leftItems[item.id] = itemView + } + + let itemSize = itemView.update( + transition: itemTransition, + component: item.component, + environment: {}, + containerSize: CGSize(width: availableWidth, height: expandedHeight) + ) + leftItemTransitions[item.id] = (itemSize, itemTransition) + availableWidth -= itemSize.width + leftItemsWidth += itemSize.width + } + + var validRightItemIds: Set = Set() + var rightItemTransitions: [AnyHashable: (CGSize, ComponentTransition)] = [:] + var rightItemsWidth: CGFloat = 0.0 + for item in component.rightItems { + validRightItemIds.insert(item.id) + var itemTransition = transition + let itemView: ComponentView + if let current = self.rightItems[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ComponentView() + self.rightItems[item.id] = itemView + } + + let itemSize = itemView.update( + transition: itemTransition, + component: item.component, + environment: {}, + containerSize: CGSize(width: availableWidth, height: expandedHeight) + ) + rightItemTransitions[item.id] = (itemSize, itemTransition) + availableWidth -= itemSize.width + rightItemsWidth += itemSize.width + } var centerLeftInset = sideInset - var leftItemX = sideInset - for item in leftItemList { - context.add(item - .position(CGPoint(x: leftItemX + item.size.width / 2.0 - (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0 + verticalOffset)) - .scale(1.0 - 0.35 * context.component.collapseFraction) - .opacity(1.0 - context.component.collapseFraction) - .appear(.default(scale: true, alpha: true)) - .disappear(.default(scale: true, alpha: true)) - ) - leftItemX += item.size.width + itemSpacing - centerLeftInset += item.size.width + itemSpacing + var leftItemX = 0.0 + for item in component.leftItems { + guard let (itemSize, itemTransition) = leftItemTransitions[item.id], let itemView = self.leftItems[item.id]?.view else { + continue + } + let itemPosition = CGPoint(x: leftItemX + itemSize.width / 2.0, y: panelHeight * 0.5) + let itemFrame = CGRect(origin: CGPoint(x: itemPosition.x - itemSize.width * 0.5, y: itemPosition.y - itemSize.height * 0.5), size: itemSize) + if itemView.superview == nil { + leftItemsBackground?.contentView.addSubview(itemView) + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + transition.animateScale(view: itemView, from: 0.01, to: 1.0) + } + itemTransition.setBounds(view: itemView, bounds: CGRect(origin: .zero, size: itemFrame.size)) + itemTransition.setPosition(view: itemView, position: itemFrame.center) + + leftItemX += itemSize.width + itemSpacing + centerLeftInset += itemSize.width + itemSpacing } - - var centerRightInset = sideInset - 5.0 - var rightItemX = context.availableSize.width - (sideInset - 5.0) - for item in rightItemList.reversed() { - context.add(item - .position(CGPoint(x: rightItemX - item.size.width / 2.0 + (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0 + verticalOffset)) - .scale(1.0 - 0.35 * context.component.collapseFraction) - .opacity(1.0 - context.component.collapseFraction) - .appear(.default(scale: true, alpha: true)) - .disappear(.default(scale: true, alpha: true)) - ) - rightItemX -= item.size.width + itemSpacing - centerRightInset += item.size.width + itemSpacing + + var centerRightInset = sideInset + var rightItemX = rightItemsWidth + for item in component.rightItems.reversed() { + guard let (itemSize, itemTransition) = rightItemTransitions[item.id], let itemView = self.rightItems[item.id]?.view else { + continue + } + let itemPosition = CGPoint(x: rightItemX - itemSize.width / 2.0, y: panelHeight * 0.5) + let itemFrame = CGRect(origin: CGPoint(x: itemPosition.x - itemSize.width * 0.5, y: itemPosition.y - itemSize.height * 0.5), size: itemSize) + if itemView.superview == nil { + rightItemsBackground?.contentView.addSubview(itemView) + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + transition.animateScale(view: itemView, from: 0.01, to: 1.0) + } + itemTransition.setBounds(view: itemView, bounds: CGRect(origin: .zero, size: itemFrame.size)) + itemTransition.setPosition(view: itemView, position: itemFrame.center) + itemTransition.setScale(view: itemView, scale: 1.0 - 0.35 * component.collapseFraction) + itemTransition.setAlpha(view: itemView, alpha: 1.0 - component.collapseFraction) + + rightItemX -= itemSize.width + itemSpacing + centerRightInset += itemSize.width + itemSpacing + } + + if let leftItemsBackground { + let leftItemsFrame = CGRect(origin: CGPoint(x: sideInset - (leftItemsWidth / 2.0 * 0.35 * component.collapseFraction), y: component.topInset + contentHeight / 2.0 + verticalOffset - panelHeight / 2.0), size: CGSize(width: leftItemsWidth, height: panelHeight)) + leftItemsBackgroundTransition.setFrame(view: leftItemsBackground, frame: leftItemsFrame) + leftItemsBackground.update(size: leftItemsFrame.size, shape: .roundedRect(cornerRadius: leftItemsFrame.height * 0.5), isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: leftItemsBackgroundTransition) + + leftItemsBackgroundTransition.setScale(view: leftItemsBackground, scale: 1.0 - 0.999 * component.collapseFraction) + leftItemsBackgroundTransition.setAlpha(view: leftItemsBackground.contentView, alpha: 1.0 - component.collapseFraction) + } else if let leftItemsBackground = self.leftItemsBackground { + self.leftItemsBackground = nil + leftItemsBackground.removeFromSuperview() + } + + if let rightItemsBackground { + let rightItemsFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - rightItemsWidth * (1.0 - component.collapseFraction) + (rightItemsWidth / 2.0 * 0.35 * component.collapseFraction), y: component.topInset + contentHeight / 2.0 + verticalOffset - panelHeight / 2.0), size: CGSize(width: rightItemsWidth, height: panelHeight)) + rightItemsBackgroundTransition.setFrame(view: rightItemsBackground, frame: rightItemsFrame) + rightItemsBackground.update(size: rightItemsFrame.size, shape: .roundedRect(cornerRadius: rightItemsFrame.height * 0.5), isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: rightItemsBackgroundTransition) + + rightItemsBackgroundTransition.setScale(view: rightItemsBackground, scale: 1.0 - 0.999 * component.collapseFraction) + rightItemsBackgroundTransition.setAlpha(view: rightItemsBackground.contentView, alpha: 1.0 - component.collapseFraction) + } else if let rightItemsBackground = self.rightItemsBackground { + self.rightItemsBackground = nil + rightItemsBackground.removeFromSuperview() + } + + var removeLeftItemIds: [AnyHashable] = [] + for (id, item) in self.leftItems { + if !validLeftItemIds.contains(id) { + removeLeftItemIds.append(id) + if let itemView = item.view { + transition.setScale(view: itemView, scale: 0.01) + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } + } + for id in removeLeftItemIds { + self.leftItems.removeValue(forKey: id) + } + + var removeRightItemIds: [AnyHashable] = [] + for (id, item) in self.rightItems { + if !validRightItemIds.contains(id) { + removeRightItemIds.append(id) + if let itemView = item.view { + transition.setScale(view: itemView, scale: 0.01) + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } + } + for id in removeRightItemIds { + self.rightItems.removeValue(forKey: id) } let maxCenterInset = max(centerLeftInset, centerRightInset) - if !leftItemList.isEmpty || !rightItemList.isEmpty { - availableWidth -= itemSpacing * CGFloat(max(0, leftItemList.count - 1)) + itemSpacing * CGFloat(max(0, rightItemList.count - 1)) + 30.0 + if !component.leftItems.isEmpty || !component.rightItems.isEmpty { + availableWidth -= itemSpacing * CGFloat(max(0, component.leftItems.count - 1)) + itemSpacing * CGFloat(max(0, component.rightItems.count - 1)) + 30.0 } - availableWidth -= context.component.sideInset * 2.0 + availableWidth -= component.sideInset * 2.0 - let canCenter = availableWidth > 660.0 - availableWidth = min(660.0, availableWidth) + let canCenter = availableWidth > 390.0 + availableWidth = min(390.0, availableWidth) - let environment = BrowserNavigationBarEnvironment(fraction: context.component.collapseFraction) + let environment = BrowserNavigationBarEnvironment(fraction: component.collapseFraction) - let centerItem = context.component.centerItem.flatMap { item in - centerItems[item.id].update( + var centerX = maxCenterInset + (availableSize.width - maxCenterInset * 2.0) / 2.0 + if canCenter { + centerX = availableSize.width / 2.0 + } else { + centerX = centerLeftInset + (availableSize.width - centerLeftInset - centerRightInset) / 2.0 + } + + var validCenterItemIds: Set = Set() + if let item = component.centerItem { + validCenterItemIds.insert(item.id) + + var itemTransition = transition + let itemView: ComponentView + if let current = self.centerItems[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ComponentView() + self.centerItems[item.id] = itemView + } + + let itemSize = itemView.update( + transition: itemTransition, component: item.component, environment: { environment }, - availableSize: CGSize(width: availableWidth, height: expandedHeight), - transition: context.transition - ) - } - - var centerX = maxCenterInset + (context.availableSize.width - maxCenterInset * 2.0) / 2.0 - if "".isEmpty { - if canCenter { - centerX = context.availableSize.width / 2.0 - } else { - centerX = centerLeftInset + (context.availableSize.width - centerLeftInset - centerRightInset) / 2.0 - } - } - if let centerItem = centerItem { - let centerItemPosition = CGPoint(x: centerX, y: context.component.topInset + contentHeight / 2.0 + verticalOffset) - context.add(centerItem - .position(centerItemPosition) - .scale(1.0 - 0.35 * context.component.collapseFraction) - .appear(.default(scale: false, alpha: true)) - .disappear(.default(scale: false, alpha: true)) + containerSize: CGSize(width: availableWidth, height: expandedHeight) ) - context.component.externalState?.centerItemFrame = centerItem.size.centered(around: centerItemPosition) + let itemPosition = CGPoint(x: centerX, y: component.topInset + contentHeight / 2.0 + verticalOffset) + let itemFrame = CGRect(origin: CGPoint(x: itemPosition.x - itemSize.width * 0.5, y: itemPosition.y - itemSize.height * 0.5), size: itemSize) + if let itemView = itemView.view { + if itemView.superview == nil { + self.containerView.contentView.addSubview(itemView) + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + } + itemTransition.setBounds(view: itemView, bounds: CGRect(origin: .zero, size: itemFrame.size)) + itemTransition.setPosition(view: itemView, position: itemFrame.center) + itemTransition.setScale(view: itemView, scale: 1.0 - 0.25 * component.collapseFraction) + } + component.externalState?.centerItemFrame = itemFrame } - if context.component.collapseFraction == 1.0 { - let activateAction = context.component.activate - let activate = activate.update( - component: Button( - content: AnyComponent(Rectangle(color: UIColor(rgb: 0x000000, alpha: 0.001))), - action: { - activateAction() - } - ), - availableSize: size, - transition: .immediate - ) - context.add(activate - .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) - ) + var removeCenterItemIds: [AnyHashable] = [] + for (id, item) in self.centerItems { + if !validCenterItemIds.contains(id) { + removeCenterItemIds.append(id) + if let itemView = item.view { + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } } + for id in removeCenterItemIds { + self.centerItems.removeValue(forKey: id) + } + + if component.collapseFraction == 1.0 { + if self.activateButton.superview == nil { + self.addSubview(self.activateButton) + } + self.activateButton.frame = CGRect(origin: .zero, size: size) + } else { + self.activateButton.removeFromSuperview() + } + + self.containerView.update(size: size, isDark: component.theme.overallDarkAppearance, transition: transition) + transition.setFrame(view: self.containerView, frame: CGRect(origin: .zero, size: size)) + + let edgeEffectHeight: CGFloat = 80.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update( + content: .clear, + blur: true, + rect: edgeEffectFrame, + edge: .top, + edgeSize: edgeEffectFrame.height, + transition: transition + ) return size } } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } } -private final class LoadingProgressComponent: Component { +final class LoadingProgressComponent: Component { let color: UIColor let height: CGFloat let value: CGFloat @@ -414,111 +536,3 @@ private final class LoadingProgressComponent: Component { return view.update(component: self, availableSize: availableSize, transition: transition) } } - -final class ReferenceButtonComponent: Component { - let content: AnyComponent - let tag: AnyObject? - let action: () -> Void - - init( - content: AnyComponent, - tag: AnyObject? = nil, - action: @escaping () -> Void - ) { - self.content = content - self.tag = tag - self.action = action - } - - static func ==(lhs: ReferenceButtonComponent, rhs: ReferenceButtonComponent) -> Bool { - if lhs.content != rhs.content { - return false - } - if lhs.tag !== rhs.tag { - return false - } - return true - } - - final class View: HighlightTrackingButton, ComponentTaggedView { - private let sourceView: ContextControllerSourceView - let referenceNode: ContextReferenceContentNode - let componentView: ComponentView - - private var component: ReferenceButtonComponent? - - public func matches(tag: Any) -> Bool { - if let component = self.component, let componentTag = component.tag { - let tag = tag as AnyObject - if componentTag === tag { - return true - } - } - return false - } - - init() { - self.componentView = ComponentView() - self.sourceView = ContextControllerSourceView() - self.sourceView.animateScale = false - self.referenceNode = ContextReferenceContentNode() - - super.init(frame: CGRect()) - - self.sourceView.isUserInteractionEnabled = false - self.addSubview(self.sourceView) - self.sourceView.addSubnode(self.referenceNode) - - self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self, let contentView = strongSelf.componentView.view { - if highlighted { - contentView.layer.removeAnimation(forKey: "opacity") - contentView.alpha = 0.4 - } else { - contentView.alpha = 1.0 - contentView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - } - - required init?(coder aDecoder: NSCoder) { - preconditionFailure() - } - - @objc private func pressed() { - self.component?.action() - } - - func update(component: ReferenceButtonComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - self.component = component - - let componentSize = self.componentView.update( - transition: transition, - component: component.content, - environment: {}, - containerSize: availableSize - ) - if let componentView = self.componentView.view { - if componentView.superview == nil { - self.referenceNode.view.addSubview(componentView) - } - transition.setFrame(view: componentView, frame: CGRect(origin: .zero, size: componentSize)) - } - - transition.setFrame(view: self.sourceView, frame: CGRect(origin: .zero, size: componentSize)) - self.referenceNode.frame = CGRect(origin: .zero, size: componentSize) - - return componentSize - } - } - - func makeView() -> View { - return View() - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) - } -} diff --git a/submodules/BrowserUI/Sources/BrowserPdfContent.swift b/submodules/BrowserUI/Sources/BrowserPdfContent.swift index 8cad615170..be32b96e3e 100644 --- a/submodules/BrowserUI/Sources/BrowserPdfContent.swift +++ b/submodules/BrowserUI/Sources/BrowserPdfContent.swift @@ -16,6 +16,7 @@ import ShareController import UndoUI import UrlEscaping import PDFKit +import GlassBackgroundComponent final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDFDocumentDelegate { private let context: AccountContext @@ -25,7 +26,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF private let pdfView: PDFView private let scrollView: UIScrollView! - private let pageIndicatorBackgorund: UIVisualEffectView + private let pageIndicatorBackground = GlassBackgroundView() private let pageIndicator = ComponentView() private var pageNumber: (Int, Int)? private var pageTimer: SwiftSignalKit.Timer? @@ -61,11 +62,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF self.pdfView = PDFView() self.pdfView.clipsToBounds = false - - self.pageIndicatorBackgorund = UIVisualEffectView(effect: UIBlurEffect(style: .light)) - self.pageIndicatorBackgorund.clipsToBounds = true - self.pageIndicatorBackgorund.layer.cornerRadius = 10.0 - + var scrollView: UIScrollView? for view in self.pdfView.subviews { if let view = view as? UIScrollView { @@ -170,7 +167,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF return } let transition = ComponentTransition.easeInOut(duration: 0.25) - transition.setAlpha(view: self.pageIndicatorBackgorund, alpha: 0.0) + transition.setAlpha(view: self.pageIndicatorBackground, alpha: 0.0) }, queue: Queue.mainQueue()) self.pageTimer?.start() } @@ -354,7 +351,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF self.validLayout = (size, insets, fullInsets) self.previousScrollingOffset = ScrollingOffsetState(value: self.scrollView.contentOffset.y, isDraggingOrDecelerating: self.scrollView.isDragging || self.scrollView.isDecelerating) - + let currentBounds = self.scrollView.bounds let offsetToBottomEdge = max(0.0, self.scrollView.contentSize.height - currentBounds.maxY) var bottomInset = insets.bottom @@ -368,23 +365,24 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF let pageIndicatorSize = self.pageIndicator.update( transition: .immediate, component: AnyComponent( - Text(text: "\(self.pageNumber?.0 ?? 1) of \(self.pageNumber?.1 ?? 1)", font: Font.with(size: 15.0, weight: .semibold, traits: .monospacedNumbers), color: self.presentationData.theme.list.itemSecondaryTextColor) + Text(text: "\(self.pageNumber?.0 ?? 1) of \(self.pageNumber?.1 ?? 1)", font: Font.with(size: 15.0, weight: .regular, traits: .monospacedNumbers), color: self.presentationData.theme.list.itemPrimaryTextColor) ), environment: {}, containerSize: size ) if let view = self.pageIndicator.view { if view.superview == nil { - self.addSubview(self.pageIndicatorBackgorund) - self.pageIndicatorBackgorund.contentView.addSubview(view) + self.addSubview(self.pageIndicatorBackground) + self.pageIndicatorBackground.contentView.addSubview(view) } - + let horizontalPadding: CGFloat = 10.0 let verticalPadding: CGFloat = 8.0 - let pageBackgroundFrame = CGRect(origin: CGPoint(x: insets.left + 20.0, y: insets.top + 16.0), size: CGSize(width: horizontalPadding * 2.0 + pageIndicatorSize.width, height: verticalPadding * 2.0 + pageIndicatorSize.height)) + let pageBackgroundFrame = CGRect(origin: CGPoint(x: insets.left + 16.0, y: insets.top + 16.0), size: CGSize(width: horizontalPadding * 2.0 + pageIndicatorSize.width, height: verticalPadding * 2.0 + pageIndicatorSize.height)) - self.pageIndicatorBackgorund.bounds = CGRect(origin: .zero, size: pageBackgroundFrame.size) - transition.setPosition(view: self.pageIndicatorBackgorund, position: pageBackgroundFrame.center) + self.pageIndicatorBackground.update(size: pageBackgroundFrame.size, cornerRadius: pageBackgroundFrame.size.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: transition) + self.pageIndicatorBackground.bounds = CGRect(origin: .zero, size: pageBackgroundFrame.size) + transition.setPosition(view: self.pageIndicatorBackground, position: pageBackgroundFrame.center) view.frame = CGRect(origin: CGPoint(x: horizontalPadding, y: verticalPadding), size: pageIndicatorSize) } @@ -459,7 +457,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF } let transition = ComponentTransition.easeInOut(duration: 0.1) - transition.setAlpha(view: self.pageIndicatorBackgorund, alpha: 1.0) + transition.setAlpha(view: self.pageIndicatorBackground, alpha: 1.0) self.pageTimer?.invalidate() self.pageTimer = nil diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index aeb2e445d6..9eac12ee87 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -20,6 +20,7 @@ import InstantPageUI import NavigationStackComponent import LottieComponent import WebKit +import GlassBarButtonComponent private let settingsTag = GenericComponentViewTag() @@ -85,6 +86,8 @@ private final class BrowserScreenComponent: CombinedComponent { let navigationBarExternalState = BrowserNavigationBarComponent.ExternalState() + let moreButtonPlayOnce = ActionSlot() + return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let performAction = context.component.performAction @@ -123,6 +126,8 @@ private final class BrowserScreenComponent: CombinedComponent { url: context.component.contentState?.url ?? "", isSecure: context.component.contentState?.isSecure ?? false, isExpanded: context.component.presentationState.addressFocused, + readingProgress: context.component.contentState?.readingProgress ?? 0.0, + loadingProgress: context.component.contentState?.estimatedProgress, performAction: performAction ) ) @@ -134,7 +139,9 @@ private final class BrowserScreenComponent: CombinedComponent { component: AnyComponent( TitleBarContentComponent( theme: environment.theme, - title: title + title: title, + readingProgress: context.component.contentState?.readingProgress ?? 0.0, + loadingProgress: context.component.contentState?.estimatedProgress ) ) ) @@ -150,37 +157,40 @@ private final class BrowserScreenComponent: CombinedComponent { component: AnyComponent( Button( content: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.WebBrowser_Done, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.accentTextColor, paragraphAlignment: .center)), horizontalAlignment: .left, maximumNumberOfLines: 1) + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) ), action: { performAction.invoke(.close) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ) ] if isTablet { - #if DEBUG - navigationLeftItems.append( - AnyComponentWithIdentity( - id: "minimize", - component: AnyComponent( - Button( - content: AnyComponent( - BundleIconComponent( - name: "Media Gallery/PictureInPictureButton", - tintColor: environment.theme.rootController.navigationBar.accentTextColor - ) - ), - action: { - performAction.invoke(.close) - } - ) - ) - ) - ) - #endif +// #if DEBUG +// navigationLeftItems.append( +// AnyComponentWithIdentity( +// id: "minimize", +// component: AnyComponent( +// Button( +// content: AnyComponent( +// BundleIconComponent( +// name: "Media Gallery/PictureInPictureButton", +// tintColor: environment.theme.rootController.navigationBar.accentTextColor +// ) +// ), +// action: { +// performAction.invoke(.close) +// } +// ) +// ) +// ) +// ) +// #endif let canGoBack = context.component.contentState?.canGoBack ?? false let canGoForward = context.component.contentState?.canGoForward ?? false @@ -193,13 +203,13 @@ private final class BrowserScreenComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Back", - tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoBack ? 1.0 : 0.4) + tintColor: environment.theme.chat.inputPanel.panelControlColor.withAlphaComponent(canGoBack ? 1.0 : 0.4) ) ), action: { performAction.invoke(.navigateBack) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ) ) @@ -212,13 +222,13 @@ private final class BrowserScreenComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Forward", - tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoForward ? 1.0 : 0.4) + tintColor: environment.theme.chat.inputPanel.panelControlColor.withAlphaComponent(canGoForward ? 1.0 : 0.4) ) ), action: { performAction.invoke(.navigateForward) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ) ) @@ -228,21 +238,22 @@ private final class BrowserScreenComponent: CombinedComponent { AnyComponentWithIdentity( id: "settings", component: AnyComponent( - ReferenceButtonComponent( + Button( content: AnyComponent( LottieComponent( content: LottieComponent.AppBundleContent( - name: "anim_moredots" + name: "anim_morewide" ), - color: environment.theme.rootController.navigationBar.accentTextColor, - size: CGSize(width: 30.0, height: 30.0) + color: environment.theme.chat.inputPanel.panelControlColor, + size: CGSize(width: 34.0, height: 34.0), + playOnce: moreButtonPlayOnce ) ), - tag: settingsTag, action: { performAction.invoke(.openSettings) + moreButtonPlayOnce.invoke(Void()) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(settingsTag) ) ) ] @@ -256,13 +267,13 @@ private final class BrowserScreenComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Bookmark", - tintColor: environment.theme.rootController.navigationBar.accentTextColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) ), action: { performAction.invoke(.openBookmarks) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ), at: 0 @@ -275,14 +286,14 @@ private final class BrowserScreenComponent: CombinedComponent { Button( content: AnyComponent( BundleIconComponent( - name: "Chat List/NavigationShare", - tintColor: environment.theme.rootController.navigationBar.accentTextColor + name: "Instant View/Share", + tintColor: environment.theme.chat.inputPanel.panelControlColor ) ), action: { performAction.invoke(.share) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ), at: 0 @@ -297,13 +308,13 @@ private final class BrowserScreenComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Browser", - tintColor: environment.theme.rootController.navigationBar.accentTextColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) ), action: { performAction.invoke(.openIn) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ) ) @@ -316,21 +327,15 @@ private final class BrowserScreenComponent: CombinedComponent { let navigationBar = navigationBar.update( component: BrowserNavigationBarComponent( - backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor, - separatorColor: environment.theme.rootController.navigationBar.separatorColor, - textColor: environment.theme.rootController.navigationBar.primaryTextColor, - progressColor: environment.theme.rootController.navigationBar.segmentedBackgroundColor, - accentColor: environment.theme.rootController.navigationBar.accentTextColor, + theme: environment.theme, topInset: environment.statusBarHeight, - height: environment.navigationHeight - environment.statusBarHeight, + height: environment.navigationHeight - environment.statusBarHeight + 8.0, sideInset: environment.safeInsets.left, metrics: environment.metrics, externalState: navigationBarExternalState, leftItems: navigationLeftItems, rightItems: navigationRightItems, centerItem: navigationContent, - readingProgress: context.component.contentState?.readingProgress ?? 0.0, - loadingProgress: context.component.contentState?.estimatedProgress, collapseFraction: collapseFraction, activate: { performAction.invoke(.expand) @@ -339,9 +344,6 @@ private final class BrowserScreenComponent: CombinedComponent { availableSize: context.availableSize, transition: context.transition ) - context.add(navigationBar - .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0)) - ) let toolbarContent: AnyComponentWithIdentity? if context.component.presentationState.isSearching { @@ -349,8 +351,8 @@ private final class BrowserScreenComponent: CombinedComponent { id: "search", component: AnyComponent( SearchToolbarContentComponent( + theme: environment.theme, strings: environment.strings, - textColor: environment.theme.rootController.navigationBar.primaryTextColor, index: context.component.presentationState.searchResultIndex, count: context.component.presentationState.searchResultCount, isEmpty: context.component.presentationState.searchQueryIsEmpty, @@ -363,8 +365,7 @@ private final class BrowserScreenComponent: CombinedComponent { id: "navigation", component: AnyComponent( NavigationToolbarContentComponent( - accentColor: environment.theme.rootController.navigationBar.accentTextColor, - textColor: environment.theme.rootController.navigationBar.primaryTextColor, + theme: environment.theme, canGoBack: context.component.contentState?.canGoBack ?? false, canGoForward: context.component.contentState?.canGoForward ?? false, canOpenIn: canOpenIn, @@ -384,15 +385,12 @@ private final class BrowserScreenComponent: CombinedComponent { toolbarBottomInset = environment.safeInsets.bottom } - var toolbarSize: CGFloat = 0.0 if isTablet && !context.component.presentationState.isSearching { } else { let toolbar = toolbar.update( component: BrowserToolbarComponent( - backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor, - separatorColor: environment.theme.rootController.navigationBar.separatorColor, - textColor: environment.theme.rootController.navigationBar.primaryTextColor, + theme: environment.theme, bottomInset: toolbarBottomInset, sideInset: environment.safeInsets.left, item: toolbarContent, @@ -412,16 +410,9 @@ private final class BrowserScreenComponent: CombinedComponent { }) }) ) - toolbarSize = toolbar.size.height } if context.component.presentationState.addressFocused { - let addressListSize: CGSize - if isTablet { - addressListSize = context.availableSize - } else { - addressListSize = CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbarSize) - } let controller = environment.controller let addressList = addressList.update( component: BrowserAddressListComponent( @@ -431,12 +422,13 @@ private final class BrowserScreenComponent: CombinedComponent { insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right), metrics: environment.metrics, addressBarFrame: navigationBarExternalState.centerItemFrame, + navigationBarHeight: navigationBar.size.height, performAction: performAction, presentInGlobalOverlay: { c in controller()?.presentInGlobalOverlay(c) } ), - availableSize: addressListSize, + availableSize: context.availableSize, transition: context.transition ) @@ -448,7 +440,7 @@ private final class BrowserScreenComponent: CombinedComponent { ) } else { context.add(addressList - .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: addressList.size.height / 2.0)) .clipsToBounds(true) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) @@ -456,6 +448,10 @@ private final class BrowserScreenComponent: CombinedComponent { } } + context.add(navigationBar + .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0)) + ) + return context.availableSize } } @@ -1075,7 +1071,7 @@ public class BrowserScreen: ViewController, MinimizableController { } func openSettings() { - guard let referenceView = self.componentHost.findTaggedView(tag: settingsTag) as? ReferenceButtonComponent.View else { + guard let referenceView = self.componentHost.findTaggedView(tag: settingsTag) else { return } @@ -1083,10 +1079,6 @@ public class BrowserScreen: ViewController, MinimizableController { return } - if let animationComponentView = referenceView.componentView.view as? LottieComponent.View { - animationComponentView.playOnce() - } - if let webContent = content as? BrowserWebContent { webContent.requestInstantView() } @@ -1103,7 +1095,7 @@ public class BrowserScreen: ViewController, MinimizableController { } } - let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView.referenceNode.view)) + let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView)) let items: Signal = combineLatest( queue: Queue.mainQueue(), @@ -1549,6 +1541,8 @@ public class BrowserScreen: ViewController, MinimizableController { super.init(navigationBarPresentationData: nil) + self._hasGlassStyle = true + self.navigationPresentation = .modalInCompactLayout self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) @@ -1731,7 +1725,7 @@ private final class BrowserContentComponent: Component { self.addSubview(component.content) } - let collapsedHeight: CGFloat = 24.0 + let collapsedHeight: CGFloat = 54.0 let topInset: CGFloat = component.navigationBarHeight * (1.0 - component.scrollingPanelOffsetFraction) + (component.insets.top + collapsedHeight) * component.scrollingPanelOffsetFraction let bottomInset = component.hasBottomPanel ? (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction) : 0.0 let insets = UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right) diff --git a/submodules/BrowserUI/Sources/BrowserSearchBarComponent.swift b/submodules/BrowserUI/Sources/BrowserSearchBarComponent.swift index 9678ab3b06..104ecaaa5c 100644 --- a/submodules/BrowserUI/Sources/BrowserSearchBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserSearchBarComponent.swift @@ -7,6 +7,7 @@ import ComponentFlow import TelegramPresentationData import AccountContext import BundleIconComponent +import SearchInputPanelComponent final class SearchBarContentComponent: Component { public typealias EnvironmentType = BrowserNavigationBarEnvironment @@ -35,112 +36,16 @@ final class SearchBarContentComponent: Component { return true } - final class View: UIView, UITextFieldDelegate { - private final class SearchTextField: UITextField { - override func textRect(forBounds bounds: CGRect) -> CGRect { - return bounds.integral - } - } - - private struct Params: Equatable { - var theme: PresentationTheme - var strings: PresentationStrings - var size: CGSize - - static func ==(lhs: Params, rhs: Params) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.size != rhs.size { - return false - } - return true - } - } - + final class View: UIView { private let queryPromise = ValuePromise() private var queryDisposable: Disposable? - private let backgroundLayer: SimpleLayer + private let searchInput = ComponentView() - private let iconView: UIImageView - - private let clearIconView: UIImageView - private let clearIconButton: HighlightTrackingButton - - private let cancelButtonTitle: ComponentView - private let cancelButton: HighlightTrackingButton - - private var placeholderContent = ComponentView() - - private var textFrame: CGRect? - private var textField: SearchTextField? - - private var tapRecognizer: UITapGestureRecognizer? - - private var params: Params? private var component: SearchBarContentComponent? - init() { - self.backgroundLayer = SimpleLayer() - - self.iconView = UIImageView() - - self.clearIconView = UIImageView() - self.clearIconButton = HighlightableButton() - self.clearIconView.isHidden = true - self.clearIconButton.isHidden = true - - self.cancelButtonTitle = ComponentView() - self.cancelButton = HighlightTrackingButton() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.backgroundLayer) - - self.addSubview(self.iconView) - self.addSubview(self.clearIconView) - self.addSubview(self.clearIconButton) - - self.addSubview(self.cancelButton) - self.clipsToBounds = true - - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) - self.tapRecognizer = tapRecognizer - self.addGestureRecognizer(tapRecognizer) - - self.cancelButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.layer.removeAnimation(forKey: "opacity") - cancelButtonTitleView.alpha = 0.4 - } - } else { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.alpha = 1.0 - cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside) - - self.clearIconButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.clearIconView.layer.removeAnimation(forKey: "opacity") - strongSelf.clearIconView.alpha = 0.4 - } else { - strongSelf.clearIconView.alpha = 1.0 - strongSelf.clearIconView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.clearIconButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside) + override init(frame: CGRect) { + super.init(frame: frame) let throttledSearchQuery = self.queryPromise.get() |> mapToSignal { query -> Signal in @@ -164,194 +69,46 @@ final class SearchBarContentComponent: Component { fatalError("init(coder:) has not been implemented") } - @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.activateTextInput() - } - } - - private func activateTextInput() { - if self.textField == nil, let textFrame = self.textFrame { - let backgroundFrame = self.backgroundLayer.frame - let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX - 32.0, height: backgroundFrame.height)) - - let textField = SearchTextField(frame: textFieldFrame) - textField.clipsToBounds = true - textField.autocorrectionType = .no - textField.returnKeyType = .search - self.textField = textField - self.insertSubview(textField, belowSubview: self.clearIconView) - textField.delegate = self - textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) - } - - guard !(self.textField?.isFirstResponder ?? false) else { - return - } - - self.textField?.becomeFirstResponder() - } - - @objc private func cancelPressed() { - self.clearIconView.isHidden = true - self.clearIconButton.isHidden = true - - let textField = self.textField - self.textField = nil - - self.component?.performAction.invoke(.updateSearchActive(false)) - - if let textField { - textField.resignFirstResponder() - textField.removeFromSuperview() - } - } - - @objc private func clearPressed() { - guard let textField = self.textField else { - return - } - textField.text = "" - self.textFieldChanged(textField) - } - - func deactivate() { - if let text = self.textField?.text, !text.isEmpty { - self.textField?.endEditing(true) - } else { - self.cancelPressed() - } - } - - public func textFieldDidBeginEditing(_ textField: UITextField) { - } - - public func textFieldDidEndEditing(_ textField: UITextField) { - } - - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.endEditing(true) - return false - } - - @objc private func textFieldChanged(_ textField: UITextField) { - let text = textField.text ?? "" - - self.clearIconView.isHidden = text.isEmpty - self.clearIconButton.isHidden = text.isEmpty - self.placeholderContent.view?.isHidden = !text.isEmpty - - self.queryPromise.set(text) - - if let params = self.params { - self.update(theme: params.theme, strings: params.strings, size: params.size, transition: .immediate) - } - } - func update(component: SearchBarContentComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { self.component = component - self.update(theme: component.theme, strings: component.strings, size: availableSize, transition: transition) - self.activateTextInput() - - return availableSize - } - - public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, transition: ComponentTransition) { - let params = Params( - theme: theme, - strings: strings, - size: size - ) - - if self.params == params { - return - } - - let isActiveWithText = true - - if self.params?.theme !== theme { - self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.iconView.tintColor = theme.rootController.navigationSearchBar.inputIconColor - self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.clearIconView.tintColor = theme.rootController.navigationSearchBar.inputClearButtonColor - } - - self.params = params - - let sideInset: CGFloat = 10.0 - let inputHeight: CGFloat = 36.0 - let topInset: CGFloat = (size.height - inputHeight) / 2.0 - - let sideTextInset: CGFloat = sideInset + 4.0 + 17.0 - - self.backgroundLayer.backgroundColor = theme.rootController.navigationSearchBar.inputFillColor.cgColor - self.backgroundLayer.cornerRadius = 10.5 - - let cancelTextSize = self.cancelButtonTitle.update( - transition: .immediate, - component: AnyComponent(Text( - text: strings.Common_Cancel, - font: Font.regular(17.0), - color: theme.rootController.navigationBar.accentTextColor - )), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: 100.0) - ) - - let cancelButtonSpacing: CGFloat = 8.0 - - var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight)) - if isActiveWithText { - backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing - } - transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) - - transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) - - let textX: CGFloat = backgroundFrame.minX + sideTextInset - let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) - self.textFrame = textFrame - - if let image = self.iconView.image { - let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + 5.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setFrame(view: self.iconView, frame: iconFrame) - } - - let placeholderSize = self.placeholderContent.update( + let searchInputSize = self.searchInput.update( transition: transition, component: AnyComponent( - Text(text: strings.Common_Search, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + SearchInputPanelComponent( + theme: component.theme, + strings: component.strings, + metrics: .init(widthClass: .compact, heightClass: .compact, orientation: nil), + safeInsets: UIEdgeInsets(), + placeholder: component.strings.Common_Search, + hasEdgeEffect: false, + updated: { [weak self] query in + guard let self else { + return + } + self.queryPromise.set(query) + }, + cancel: { [weak self] in + guard let self else { + return + } + self.component?.performAction.invoke(.updateSearchActive(false)) + } + ) ), environment: {}, - containerSize: size + containerSize: availableSize ) - if let placeholderContentView = self.placeholderContent.view { - if placeholderContentView.superview == nil { - self.addSubview(placeholderContentView) + if let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { + if searchInputView.superview == nil { + self.addSubview(searchInputView) + + searchInputView.activateInput() } - let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.midY - placeholderSize.height / 2.0), size: placeholderSize) - transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) - } - - if let image = self.clearIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setFrame(view: self.clearIconView, frame: iconFrame) - transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0)) - } - - if let cancelButtonTitleComponentView = self.cancelButtonTitle.view { - if cancelButtonTitleComponentView.superview == nil { - self.addSubview(cancelButtonTitleComponentView) - cancelButtonTitleComponentView.isUserInteractionEnabled = false - } - transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) - } - - if let textField = self.textField { - textField.textColor = theme.rootController.navigationSearchBar.inputTextColor - transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height))) + transition.setFrame(view: searchInputView, frame: CGRect(origin: .zero, size: searchInputSize)) } + + return availableSize } } diff --git a/submodules/BrowserUI/Sources/BrowserTitleBarComponent.swift b/submodules/BrowserUI/Sources/BrowserTitleBarComponent.swift index a362da7d61..d21be0faa0 100644 --- a/submodules/BrowserUI/Sources/BrowserTitleBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserTitleBarComponent.swift @@ -9,19 +9,26 @@ import AccountContext import BundleIconComponent import MultilineTextComponent import UrlEscaping +import GlassBackgroundComponent final class TitleBarContentComponent: Component { public typealias EnvironmentType = BrowserNavigationBarEnvironment let theme: PresentationTheme let title: String + let readingProgress: CGFloat + let loadingProgress: Double? init( theme: PresentationTheme, - title: String + title: String, + readingProgress: CGFloat, + loadingProgress: Double? ) { self.theme = theme self.title = title + self.readingProgress = readingProgress + self.loadingProgress = loadingProgress } static func ==(lhs: TitleBarContentComponent, rhs: TitleBarContentComponent) -> Bool { @@ -31,15 +38,30 @@ final class TitleBarContentComponent: Component { if lhs.title != rhs.title { return false } + if lhs.readingProgress != rhs.readingProgress { + return false + } + if lhs.loadingProgress != rhs.loadingProgress { + return false + } return true } final class View: UIView { + private let backgroundView = GlassBackgroundView() + private let clippingView = UIView() + private let readingProgressView = UIView() private var titleContent = ComponentView() private var component: TitleBarContentComponent? init() { super.init(frame: CGRect()) + + self.clippingView.clipsToBounds = true + + self.addSubview(self.backgroundView) + self.backgroundView.contentView.addSubview(self.clippingView) + self.clippingView.addSubview(self.readingProgressView) } required public init?(coder: NSCoder) { @@ -48,7 +70,9 @@ final class TitleBarContentComponent: Component { func update(component: TitleBarContentComponent, availableSize: CGSize, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component - + + let collapseFraction = environment[BrowserNavigationBarEnvironment.self].fraction + let titleSize = self.titleContent.update( transition: transition, component: AnyComponent( @@ -60,7 +84,7 @@ final class TitleBarContentComponent: Component { ) ), environment: {}, - containerSize: CGSize(width: availableSize.width - 36.0, height: availableSize.height) + containerSize: CGSize(width: availableSize.width - 42.0, height: availableSize.height) ) let titleContentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - titleSize.height) / 2.0)), size: titleSize) if let titleContentView = self.titleContent.view { @@ -71,6 +95,18 @@ final class TitleBarContentComponent: Component { titleContentView.bounds = CGRect(origin: .zero, size: titleContentFrame.size) } + let expandedBackgroundWidth = availableSize.width - 14.0 * 2.0 + let collapsedBackgroundWidth = titleSize.width + 32.0 + let backgroundSize = CGSize(width: expandedBackgroundWidth * (1.0 - collapseFraction) + collapsedBackgroundWidth * collapseFraction, height: 44.0) + self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: transition) + let backgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - backgroundSize.width) / 2.0), y: floor((availableSize.height - backgroundSize.height) / 2.0)), size: backgroundSize) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + transition.setFrame(view: self.clippingView, frame: CGRect(origin: .zero, size: backgroundFrame.size)) + transition.setCornerRadius(layer: self.clippingView.layer, cornerRadius: backgroundFrame.size.height * 0.5) + + self.readingProgressView.backgroundColor = component.theme.rootController.navigationBar.primaryTextColor.withMultipliedAlpha(0.07) + self.readingProgressView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundSize.width * component.readingProgress, height: backgroundSize.height)) + return availableSize } } diff --git a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift index 10834cdfd0..8f695ffc23 100644 --- a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift @@ -6,28 +6,24 @@ import BlurredBackgroundComponent import BundleIconComponent import TelegramPresentationData import ContextReferenceButtonComponent +import GlassBackgroundComponent +import EdgeEffect final class BrowserToolbarComponent: CombinedComponent { - let backgroundColor: UIColor - let separatorColor: UIColor - let textColor: UIColor + let theme: PresentationTheme let bottomInset: CGFloat let sideInset: CGFloat let item: AnyComponentWithIdentity? let collapseFraction: CGFloat init( - backgroundColor: UIColor, - separatorColor: UIColor, - textColor: UIColor, + theme: PresentationTheme, bottomInset: CGFloat, sideInset: CGFloat, item: AnyComponentWithIdentity?, collapseFraction: CGFloat ) { - self.backgroundColor = backgroundColor - self.separatorColor = separatorColor - self.textColor = textColor + self.theme = theme self.bottomInset = bottomInset self.sideInset = sideInset self.item = item @@ -35,13 +31,7 @@ final class BrowserToolbarComponent: CombinedComponent { } static func ==(lhs: BrowserToolbarComponent, rhs: BrowserToolbarComponent) -> Bool { - if lhs.backgroundColor != rhs.backgroundColor { - return false - } - if lhs.separatorColor != rhs.separatorColor { - return false - } - if lhs.textColor != rhs.textColor { + if lhs.theme !== rhs.theme { return false } if lhs.bottomInset != rhs.bottomInset { @@ -60,54 +50,70 @@ final class BrowserToolbarComponent: CombinedComponent { } static var body: Body { - let background = Child(BlurredBackgroundComponent.self) - let separator = Child(Rectangle.self) + let edgeEffect = Child(EdgeEffectComponent.self) + let background = Child(GlassBackgroundComponent.self) let centerItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) return { context in - let contentHeight: CGFloat = 49.0 + let contentHeight: CGFloat = 56.0 let totalHeight = contentHeight + context.component.bottomInset let offset = context.component.collapseFraction * totalHeight let size = CGSize(width: context.availableSize.width, height: totalHeight) - let background = background.update( - component: BlurredBackgroundComponent(color: context.component.backgroundColor), - availableSize: CGSize(width: size.width, height: size.height), + let backgroundHeight: CGFloat = 48.0 + let edgeEffectHeight = totalHeight + let edgeEffect = edgeEffect.update( + component: EdgeEffectComponent( + color: .clear, + blur: true, + alpha: 1.0, + size: CGSize(width: size.width, height: edgeEffectHeight), + edge: .bottom, + edgeSize: edgeEffectHeight + ), + availableSize: CGSize(width: size.width, height: edgeEffectHeight), transition: context.transition ) - - let separator = separator.update( - component: Rectangle(color: context.component.separatorColor, height: UIScreenPixel), - availableSize: CGSize(width: size.width, height: size.height), - transition: context.transition + context.add(edgeEffect + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 + offset)) ) - + let item = context.component.item.flatMap { item in return centerItems[item.id].update( component: item.component, - availableSize: CGSize(width: context.availableSize.width - context.component.sideInset * 2.0, height: contentHeight), + availableSize: CGSize(width: context.availableSize.width - context.component.sideInset * 2.0, height: backgroundHeight), transition: context.transition ) } + let contentWidth = item?.size.width ?? 0.0 + + let backgroundSize = CGSize(width: contentWidth, height: backgroundHeight) + let background = background.update( + component: GlassBackgroundComponent( + size: backgroundSize, + cornerRadius: backgroundHeight * 0.5, + isDark: context.component.theme.overallDarkAppearance, + tintColor: .init(kind: .panel, color: UIColor(white: context.component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), + isInteractive: true + ), + availableSize: backgroundSize, + transition: context.transition + ) context.add(background - .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 + offset)) + .position(CGPoint(x: size.width / 2.0, y: backgroundSize.height / 2.0 + offset)) ) - context.add(separator - .position(CGPoint(x: size.width / 2.0, y: 0.0 + offset)) - ) - if let centerItem = item { context.add(centerItem - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight / 2.0 + offset)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: backgroundSize.height / 2.0 + offset)) .appear(ComponentTransition.Appear({ _, view, transition in - transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: size.height), to: .zero, additive: true) + transition.animateBlur(layer: view.layer, fromRadius: 10.0, toRadius: 0.0) + transition.animateAlpha(view: view, from: 0.0, to: 1.0) })) .disappear(ComponentTransition.Disappear({ view, transition, completion in - let from = view.center - view.center = from.offsetBy(dx: 0.0, dy: size.height) - transition.animatePosition(view: view, from: from, to: view.center, completion: { _ in + transition.animateBlur(layer: view.layer, fromRadius: 0.0, toRadius: 10.0) + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in completion() }) })) @@ -120,8 +126,7 @@ final class BrowserToolbarComponent: CombinedComponent { } final class NavigationToolbarContentComponent: CombinedComponent { - let accentColor: UIColor - let textColor: UIColor + let theme: PresentationTheme let canGoBack: Bool let canGoForward: Bool let canOpenIn: Bool @@ -131,8 +136,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { let performHoldAction: (UIView, ContextGesture?, BrowserScreen.Action) -> Void init( - accentColor: UIColor, - textColor: UIColor, + theme: PresentationTheme, canGoBack: Bool, canGoForward: Bool, canOpenIn: Bool, @@ -141,8 +145,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { performAction: ActionSlot, performHoldAction: @escaping (UIView, ContextGesture?, BrowserScreen.Action) -> Void ) { - self.accentColor = accentColor - self.textColor = textColor + self.theme = theme self.canGoBack = canGoBack self.canGoForward = canGoForward self.canOpenIn = canOpenIn @@ -153,10 +156,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { } static func ==(lhs: NavigationToolbarContentComponent, rhs: NavigationToolbarContentComponent) -> Bool { - if lhs.accentColor != rhs.accentColor { - return false - } - if lhs.textColor != rhs.textColor { + if lhs.theme !== rhs.theme { return false } if lhs.canGoBack != rhs.canGoBack { @@ -191,26 +191,20 @@ final class NavigationToolbarContentComponent: CombinedComponent { let performAction = context.component.performAction let performHoldAction = context.component.performHoldAction - let sideInset: CGFloat = 5.0 - let buttonSize = CGSize(width: 50.0, height: availableSize.height) + var size = CGSize(width: 0.0, height: 48.0) + let buttonSize = CGSize(width: 50.0, height: size.height) - var buttonCount = 3 - if context.component.canShare { - buttonCount += 1 - } - if context.component.canOpenIn { - buttonCount += 1 - } - - let spacing = (availableSize.width - buttonSize.width * CGFloat(buttonCount) - sideInset * 2.0) / CGFloat(buttonCount - 1) + let sideInset: CGFloat = 34.0 + let spacing: CGFloat = 66.0 + let textColor = context.component.theme.rootController.navigationBar.primaryTextColor let canShare = context.component.canShare let share = share.update( component: Button( content: AnyComponent( BundleIconComponent( - name: "Chat List/NavigationShare", - tintColor: context.component.accentColor + name: "Instant View/Share", + tintColor: textColor ) ), action: { @@ -224,22 +218,14 @@ final class NavigationToolbarContentComponent: CombinedComponent { ) if context.component.isDocument { - if !context.component.canShare { - context.add(share - .position(CGPoint(x: availableSize.width / 2.0, y: 10000.0)) - ) - } else { - context.add(share - .position(CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) - ) - } - + var originX: CGFloat = sideInset + let search = search.update( component: Button( content: AnyComponent( BundleIconComponent( - name: "Chat List/SearchIcon", - tintColor: context.component.accentColor + name: "Instant View/Search", + tintColor: textColor ) ), action: { @@ -250,15 +236,27 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(search - .position(CGPoint(x: sideInset + search.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) + originX += spacing + + if !context.component.canShare { + context.add(share + .position(CGPoint(x: availableSize.width / 2.0, y: 10000.0)) + ) + } else { + context.add(share + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) + ) + originX += spacing + } let quickLook = quickLook.update( component: Button( content: AnyComponent( BundleIconComponent( name: "Instant View/OpenDocument", - tintColor: context.component.accentColor + tintColor: textColor ) ), action: { @@ -269,16 +267,19 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(quickLook - .position(CGPoint(x: context.availableSize.width - sideInset - quickLook.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) + size.width = originX + sideInset } else { + var originX: CGFloat = sideInset + let canGoBack = context.component.canGoBack let back = back.update( component: ContextReferenceButtonComponent( content: AnyComponent( BundleIconComponent( name: "Instant View/Back", - tintColor: canGoBack ? context.component.accentColor : context.component.accentColor.withAlphaComponent(0.4) + tintColor: canGoBack ? textColor : textColor.withAlphaComponent(0.4) ) ), minSize: buttonSize, @@ -297,8 +298,9 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(back - .position(CGPoint(x: sideInset + back.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: sideInset, y: availableSize.height / 2.0)) ) + originX += spacing let canGoForward = context.component.canGoForward let forward = forward.update( @@ -306,7 +308,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Forward", - tintColor: canGoForward ? context.component.accentColor : context.component.accentColor.withAlphaComponent(0.4) + tintColor: canGoForward ? textColor : textColor.withAlphaComponent(0.4) ) ), minSize: buttonSize, @@ -325,19 +327,21 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(forward - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) + originX += spacing context.add(share - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) + originX += spacing let bookmark = bookmark.update( component: Button( content: AnyComponent( BundleIconComponent( name: "Instant View/Bookmark", - tintColor: context.component.accentColor + tintColor: textColor ) ), action: { @@ -348,16 +352,18 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(bookmark - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) if context.component.canOpenIn { + originX += spacing + let openIn = openIn.update( component: Button( content: AnyComponent( BundleIconComponent( name: "Instant View/Browser", - tintColor: context.component.accentColor + tintColor: textColor ) ), action: { @@ -368,34 +374,36 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(openIn - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width + spacing + openIn.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) } + + size.width = originX + sideInset } - return availableSize + return size } } } final class SearchToolbarContentComponent: CombinedComponent { + let theme: PresentationTheme let strings: PresentationStrings - let textColor: UIColor let index: Int let count: Int let isEmpty: Bool let performAction: ActionSlot init( + theme: PresentationTheme, strings: PresentationStrings, - textColor: UIColor, index: Int, count: Int, isEmpty: Bool, performAction: ActionSlot ) { + self.theme = theme self.strings = strings - self.textColor = textColor self.index = index self.count = count self.isEmpty = isEmpty @@ -403,10 +411,10 @@ final class SearchToolbarContentComponent: CombinedComponent { } static func ==(lhs: SearchToolbarContentComponent, rhs: SearchToolbarContentComponent) -> Bool { - if lhs.strings !== rhs.strings { + if lhs.theme !== rhs.theme { return false } - if lhs.textColor != rhs.textColor { + if lhs.strings !== rhs.strings { return false } if lhs.index != rhs.index { @@ -430,15 +438,17 @@ final class SearchToolbarContentComponent: CombinedComponent { let availableSize = context.availableSize let performAction = context.component.performAction - let sideInset: CGFloat = 3.0 + let sideInset: CGFloat = 60.0 let buttonSize = CGSize(width: 50.0, height: availableSize.height) + let textColor = context.component.theme.rootController.navigationBar.primaryTextColor + let down = down.update( component: Button( content: AnyComponent( BundleIconComponent( name: "Chat/Input/Search/DownButton", - tintColor: context.component.textColor + tintColor: textColor ) ), isEnabled: context.component.count > 0, @@ -458,7 +468,7 @@ final class SearchToolbarContentComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Chat/Input/Search/UpButton", - tintColor: context.component.textColor + tintColor: textColor ) ), isEnabled: context.component.count > 0, @@ -486,7 +496,7 @@ final class SearchToolbarContentComponent: CombinedComponent { component: Text( text: currentText, font: Font.regular(15.0), - color: context.component.textColor + color: textColor ), availableSize: availableSize, transition: .easeInOut(duration: 0.2) @@ -495,7 +505,7 @@ final class SearchToolbarContentComponent: CombinedComponent { .position(CGPoint(x: availableSize.width - sideInset - down.size.width - up.size.width - text.size.width / 2.0, y: availableSize.height / 2.0)) ) - return availableSize + return CGSize(width: availableSize.width - 60.0, height: 48.0) } } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index ffaebd35a9..455ff48ac9 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -574,7 +574,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[1235637404] = { return Api.MediaArea.parse_mediaAreaWeather($0) } dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[-1188071729] = { return Api.Message.parse_message($0) } + dict[-1665888023] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[2055212554] = { return Api.Message.parse_messageService($0) } dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 4bb0329f83..f5687176a7 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -732,15 +732,15 @@ public extension Api { } public extension Api { indirect enum Message: TypeConstructorDescription { - case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?, scheduleRepeatPeriod: Int32?) + case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?, scheduleRepeatPeriod: Int32?, summaryFromLanguage: String?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod): + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod, let summaryFromLanguage): if boxed { - buffer.appendInt32(-1188071729) + buffer.appendInt32(-1665888023) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -782,6 +782,7 @@ public extension Api { if Int(flags2) & Int(1 << 6) != 0 {serializeInt64(paidMessageStars!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 7) != 0 {suggestedPost!.serialize(buffer, true)} if Int(flags2) & Int(1 << 10) != 0 {serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 11) != 0 {serializeString(summaryFromLanguage!, buffer: buffer, boxed: false)} break case .messageEmpty(let flags, let id, let peerId): if boxed { @@ -811,8 +812,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod): - return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any), ("suggestedPost", suggestedPost as Any), ("scheduleRepeatPeriod", scheduleRepeatPeriod as Any)]) + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod, let summaryFromLanguage): + return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any), ("suggestedPost", suggestedPost as Any), ("scheduleRepeatPeriod", scheduleRepeatPeriod as Any), ("summaryFromLanguage", summaryFromLanguage as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let savedPeerId, let replyTo, let date, let action, let reactions, let ttlPeriod): @@ -911,6 +912,8 @@ public extension Api { } } var _32: Int32? if Int(_2!) & Int(1 << 10) != 0 {_32 = reader.readInt32() } + var _33: String? + if Int(_2!) & Int(1 << 11) != 0 {_33 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -943,8 +946,9 @@ public extension Api { let _c30 = (Int(_2!) & Int(1 << 6) == 0) || _30 != nil let _c31 = (Int(_2!) & Int(1 << 7) == 0) || _31 != nil let _c32 = (Int(_2!) & Int(1 << 10) == 0) || _32 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 { - return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29, paidMessageStars: _30, suggestedPost: _31, scheduleRepeatPeriod: _32) + let _c33 = (Int(_2!) & Int(1 << 11) == 0) || _33 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 { + return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29, paidMessageStars: _30, suggestedPost: _31, scheduleRepeatPeriod: _32, summaryFromLanguage: _33) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index 6b3f1c13ec..c9e087f8c6 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -9006,6 +9006,24 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func summarizeText(flags: Int32, peer: Api.InputPeer, id: Int32, toLang: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1656683294) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(toLang!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.summarizeText", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("toLang", String(describing: toLang))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.TextWithEntities? in + let reader = BufferReader(buffer) + var result: Api.TextWithEntities? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + return result + }) + } +} public extension Api.functions.messages { static func toggleBotInAttachMenu(flags: Int32, bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index d20312586a..9897b02c2c 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -240,6 +240,7 @@ private var declaredEncodables: Void = { declareEncodable(PublishedSuggestedPostMessageAttribute.self, f: { PublishedSuggestedPostMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaLiveStream.self, f: { TelegramMediaLiveStream(decoder: $0) }) declareEncodable(ScheduledRepeatAttribute.self, f: { ScheduledRepeatAttribute(decoder: $0) }) + declareEncodable(SummarizationMessageAttribute.self, f: { SummarizationMessageAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 9b2457ab72..ca8956b5db 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -128,7 +128,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -144,7 +144,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = chatPeerId.peerId var result = [peerId] @@ -279,7 +279,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -712,7 +712,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod): + case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod, summaryFromLanguage): var attributes: [MessageAttribute] = [] if (flags2 & (1 << 4)) != 0 { @@ -973,6 +973,10 @@ extension StoreMessage { attributes.append(ScheduledRepeatAttribute(repeatPeriod: scheduledRepeatPeriod)) } + if let summaryFromLanguage { + attributes.append(SummarizationMessageAttribute(fromLang: summaryFromLanguage)) + } + var entitiesAttribute: TextEntitiesMessageAttribute? if let entities = entities, !entities.isEmpty { let attribute = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)) diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 185ed044fb..56c7569465 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -104,7 +104,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break @@ -400,7 +400,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage } else if let message = messages.first, let apiMessage = result.messages.first { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud - } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { + } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud } } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index cbdf0a3a1b..869b2f9da0 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -2076,7 +2076,7 @@ public final class PendingMessageManager { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { isScheduled = true } - if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { + if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { if (flags2 & (1 << 4)) != 0 { isScheduled = true } @@ -2120,7 +2120,7 @@ public final class PendingMessageManager { namespace = Namespaces.Message.QuickReplyCloud } else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud - } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { + } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud } } diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 716fffa529..655b238028 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 336b9c4351..77f071578b 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): var namespace = namespace if (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud @@ -136,7 +136,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -149,7 +149,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, _, date, _, _, _): return date @@ -160,7 +160,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -169,7 +169,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SummarizationMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SummarizationMessageAttribute.swift new file mode 100644 index 0000000000..97e869a535 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SummarizationMessageAttribute.swift @@ -0,0 +1,41 @@ +import Foundation +import Postbox +import TelegramApi + +public final class SummarizationMessageAttribute: Equatable, MessageAttribute { + public struct Summary: Equatable, Codable { + public let text: String + public let entities: [MessageTextEntity] + } + + public let fromLang: String + public let summary: Summary? + + public init( + fromLang: String, + summary: Summary? = nil + ) { + self.fromLang = fromLang + self.summary = summary + } + + required public init(decoder: PostboxDecoder) { + self.fromLang = decoder.decodeStringForKey("fl", orElse: "") + self.summary = decoder.decodeCodable(Summary.self, forKey: "sum") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.fromLang, forKey: "fl") + encoder.encodeCodable(self.summary, forKey: "sum") + } + + public static func ==(lhs: SummarizationMessageAttribute, rhs: SummarizationMessageAttribute) -> Bool { + if lhs.fromLang != rhs.fromLang { + return false + } + if lhs.summary != rhs.summary { + return false + } + return true + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift new file mode 100644 index 0000000000..ecbbf4be6e --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift @@ -0,0 +1,71 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + +public enum SummarizeError { + case generic + case invalidMessageId + case limitExceeded + case invalidLanguage +} + +func _internal_summarizeMessage(account: Account, messageId: EngineMessage.Id, translateToLang: String?) -> Signal { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) + } + |> castError(SummarizeError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { + return .never() + } + + var flags: Int32 = 0 + if let _ = translateToLang { + flags |= (1 << 0) + } + + return account.network.request(Api.functions.messages.summarizeText(flags: flags, peer: inputPeer, id: messageId.id, toLang: translateToLang)) + |> map(Optional.init) + |> mapError { error -> SummarizeError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else if error.errorDescription == "MSG_ID_INVALID" { + return .invalidMessageId + } else if error.errorDescription == "TO_LANG_INVALID" { + return .invalidLanguage + } else { + return .generic + } + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction in + switch result { + case let .textWithEntities(text, entities): + transaction.updateMessage(messageId, update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes + + var fromLang = "" + if let attribute = attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute { + fromLang = attribute.fromLang + } + let updatedAttribute = SummarizationMessageAttribute( + fromLang: fromLang, + summary: .init(text: text, entities: messageTextEntitiesFromApiEntities(entities)) + ) + attributes = attributes.filter { !($0 is SummarizationMessageAttribute) } + attributes.append(updatedAttribute) + + return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + default: + break + } + } + |> castError(SummarizeError.self) + } + |> ignoreValues + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 9133c0a5ac..143106a105 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -604,6 +604,10 @@ public extension TelegramEngine { return _internal_togglePeerMessagesTranslationHidden(account: self.account, peerId: peerId, hidden: hidden) } + public func summarizeMessage(messageId: EngineMessage.Id, translateToLang: String?) -> Signal { + return _internal_summarizeMessage(account: self.account, messageId: messageId, translateToLang: translateToLang) + } + public func transcribeAudio(messageId: MessageId) -> Signal { return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, messageId: messageId) } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift index 61ae41f57d..06f172ab1b 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift @@ -329,7 +329,7 @@ final class ModeComponent: Component { } transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerFrame.size)) - self.liquidLensView.update(size: containerFrame.size, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: selectionFrame.height), inset: 0.0, isDark: true, isLifted: self.selectionGestureState != nil, isCollapsed: false, transition: transition) + self.liquidLensView.update(size: containerFrame.size, selectionOrigin: CGPoint(x: max(0.0, min(lensSelection.x, containerFrame.size.width - lensSelection.width)), y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: selectionFrame.height), inset: 3.0, isDark: true, isLifted: self.selectionGestureState != nil, isCollapsed: false, transition: transition) self.backgroundContainer.update(size: containerFrame.size, isDark: true, transition: .immediate) return size diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index a7b1e2ae15..67cbbb27b5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1757,11 +1757,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI needsShareButton = true } + if let _ = item.message.attributes.first(where: { $0 is SummarizationMessageAttribute }) { + needsSummarizeButton = true + } + if let peer = item.message.peers[item.message.id.peerId] { if let channel = peer as? TelegramChannel { if case .broadcast = channel.info { needsShareButton = true - needsSummarizeButton = true } } } @@ -2329,6 +2332,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } + var isSummarized = false + if item.controllerInteraction.summarizedMessageIds.contains(item.message.id) { + for attribute in item.message.attributes { + if let attribute = attribute as? SummarizationMessageAttribute, attribute.summary != nil { + isSummarized = true + } + } + } + var displayHeader = false if initialDisplayHeader { if authorNameString != nil { @@ -2356,6 +2368,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI displayHeader = true } } + if isSummarized { + displayHeader = true + } } let firstNodeTopPosition: ChatMessageBubbleRelativePosition @@ -2734,16 +2749,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI hasReply = false } - var isSummarized = false - if item.controllerInteraction.summarizedMessageIds.contains(item.message.id) { - for attribute in item.message.attributes { - if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == "sum" { - isSummarized = true - hasReply = true - } - } + if isSummarized { + hasReply = true } - + if !isInstantVideo, hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil || isSummarized) { if headerSize.height.isZero { headerSize.height += 11.0 @@ -5007,10 +5016,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let summarizeButtonNode = strongSelf.summarizeButtonNode { let buttonSize = summarizeButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments, isSummarize: true) - var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize) - if strongSelf.shareButtonNode != nil { - buttonFrame.origin.y -= buttonSize.width + 10.0 - } + var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.minY + 1.0), size: buttonSize) if let shareButtonOffset = shareButtonOffset { if incoming { @@ -5068,10 +5074,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let summarizeButtonNode = strongSelf.summarizeButtonNode { let buttonSize = summarizeButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments, isSummarize: true) - var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize) - if strongSelf.shareButtonNode != nil { - buttonFrame.origin.y -= buttonSize.width + 10.0 - } + var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.minY + 1.0), size: buttonSize) if let shareButtonOffset = shareButtonOffset { if incoming { @@ -6980,24 +6983,23 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if item.controllerInteraction.summarizedMessageIds.contains(item.message.id) { item.controllerInteraction.summarizedMessageIds.remove(item.message.id) - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, true) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) } else { item.controllerInteraction.summarizedMessageIds.insert(item.message.id) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) var needsSummarization = true for attribute in item.message.attributes { - if let attribute = attribute as? TranslationMessageAttribute, attribute.toLang == "sum" { + if let attribute = attribute as? SummarizationMessageAttribute, attribute.summary != nil { needsSummarization = false break } } if needsSummarization { - let _ = (item.context.engine.messages.translateMessages(messageIds: [item.message.id], fromLang: nil, toLang: "sum", enableLocalIfPossible: false) + let _ = (item.context.engine.messages.summarizeMessage(messageId: item.message.id, translateToLang: nil) |> deliverOnMainQueue).start(completed: { - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, true) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) }) - } else { - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, true) } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift index 9f6ac88ecb..5adf430902 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift @@ -277,7 +277,7 @@ public func chatMessagePaymentAlertController( context: context, presentationData: presentationData, updatedPresentationData: updatedPresentationData, - configuration: AlertScreen.Configuration(actionAlignment: .vertical), + configuration: AlertScreen.Configuration(actionAlignment: .vertical, allowInputInset: true), content: content, actions: [ .init(title: strings.Chat_PaidMessage_Confirm_PayForMessage(count), type: .default, action: { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 3d2194bfe7..cde7b2580c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -136,6 +136,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } private let backgroundView: MessageInlineBlockBackgroundView + private var starsView: StarsView? private var quoteIconView: UIImageView? private let contentNode: ASDisplayNode private var titleNode: TextNode? @@ -209,7 +210,6 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { var secondaryColor: UIColor? var tertiaryColor: UIColor? - var authorNameColor: UIColor? var dashSecondaryColor: UIColor? var dashTertiaryColor: UIColor? @@ -242,6 +242,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { break } + if arguments.isSummarized { + authorNameColor = nil + } + switch arguments.type { case let .bubble(incoming): titleColor = incoming ? (authorNameColor ?? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor) : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor @@ -611,9 +615,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { adjustedConstrainedTextSize.width -= textLeftInset if arguments.isSummarized { - //TODO:localize - titleString = NSAttributedString(string: "AI Summary", font: titleFont, textColor: titleColor) - messageText = NSAttributedString(string: "Tap to see original text", font: textFont, textColor: titleColor) + titleString = NSAttributedString(string: arguments.presentationData.strings.Conversation_Summary_Title, font: titleFont, textColor: titleColor) + messageText = NSAttributedString(string: arguments.presentationData.strings.Conversation_Summary_Text, font: textFont, textColor: titleColor) } let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: maxTitleNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets)) @@ -696,6 +699,11 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { node = ChatMessageReplyInfoNode() } + var animation = animation + if node.titleNode == nil { + animation = .None + } + node.previousMediaReference = updatedMediaReference //node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview @@ -934,6 +942,22 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { giftEmojiLayer.removeFromSuperlayer() } + if arguments.isSummarized { + let starsView: StarsView + if let current = node.starsView { + starsView = current + } else { + starsView = StarsView() + node.starsView = starsView + node.contentNode.view.insertSubview(starsView, at: 1) + } + starsView.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) + starsView.update(size: backgroundFrame.size, color: mainColor) + } else if let starsView = node.starsView { + node.starsView = nil + starsView.removeFromSuperview() + } + node.contentNode.frame = CGRect(origin: CGPoint(), size: size) return node @@ -1074,3 +1098,96 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { return nil } } + +private final class StarsView: UIView { + private let staticEmitterLayer = CAEmitterLayer() + + private var currentColor: UIColor? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.clipsToBounds = true + + self.layer.addSublayer(self.staticEmitterLayer) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + private func setupEmitter(size: CGSize) { + guard let currentColor = self.currentColor else { + return + } + let color = currentColor + + self.staticEmitterLayer.emitterShape = .rectangle + self.staticEmitterLayer.emitterSize = size + self.staticEmitterLayer.emitterMode = .surface + self.layer.addSublayer(self.staticEmitterLayer) + + let staticEmitter = CAEmitterCell() + staticEmitter.name = "emitter" + staticEmitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage + staticEmitter.birthRate = 20.0 + staticEmitter.lifetime = 3.2 + staticEmitter.velocity = 18.0 + staticEmitter.velocityRange = 3 + staticEmitter.scale = 0.1 + staticEmitter.scaleRange = 0.08 + staticEmitter.emissionRange = .pi * 2.0 + staticEmitter.setValue(3.0, forKey: "mass") + staticEmitter.setValue(2.0, forKey: "massRange") + + let staticColors: [Any] = [ + color.withAlphaComponent(0.0).cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor, + color.withAlphaComponent(0.0).cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + staticColorBehavior.setValue(staticColors, forKey: "colors") + staticEmitter.setValue([staticColorBehavior], forKey: "emitterBehaviors") + + let attractor = CAEmitterCell.createEmitterBehavior(type: "simpleAttractor") + attractor.setValue("attractor", forKey: "name") + attractor.setValue(20, forKey: "falloff") + attractor.setValue(35, forKey: "radius") + self.staticEmitterLayer.setValue([attractor], forKey: "emitterBehaviors") + self.staticEmitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.attractor.stiffness") + self.staticEmitterLayer.setValue(false, forKeyPath: "emitterBehaviors.attractor.enabled") + + self.staticEmitterLayer.emitterCells = [staticEmitter] + } + + func update(size: CGSize, color: UIColor) { + if self.staticEmitterLayer.emitterCells == nil { + self.currentColor = color + self.setupEmitter(size: size) + } else if self.currentColor != color { + self.currentColor = color + + let staticColors: [Any] = [ + UIColor.white.withAlphaComponent(0.0).cgColor, + UIColor.white.withAlphaComponent(0.35).cgColor, + color.cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + staticColorBehavior.setValue(staticColors, forKey: "colors") + + for cell in self.staticEmitterLayer.emitterCells ?? [] { + cell.setValue([staticColorBehavior], forKey: "emitterBehaviors") + } + } + + let emitterPosition = CGPoint(x: size.width * 0.5, y: size.height * 0.5) + self.staticEmitterLayer.frame = CGRect(origin: .zero, size: size) + self.staticEmitterLayer.emitterPosition = emitterPosition + self.staticEmitterLayer.setValue(emitterPosition, forKeyPath: "emitterBehaviors.attractor.position") + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD index 6d03e682ed..70a8cb9400 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/WallpaperBackgroundNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/ContextUI", + "//submodules/Components/HierarchyTrackingLayer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift index 981f902892..7e38d33ef3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift @@ -10,6 +10,7 @@ import Postbox import WallpaperBackgroundNode import ChatMessageItemCommon import ContextUI +import HierarchyTrackingLayer public class ChatMessageShareButton: ASDisplayNode { private let referenceNode: ContextReferenceContentNode @@ -21,10 +22,12 @@ public class ChatMessageShareButton: ASDisplayNode { private let topButton: HighlightTrackingButtonNode private let topIconNode: ASImageNode private var topIconOffset = CGPoint() - + private var bottomButton: HighlightTrackingButtonNode? private var bottomIconNode: ASImageNode? + private var starsView: StarsView? + private var separatorNode: ASDisplayNode? private var theme: PresentationTheme? @@ -180,6 +183,17 @@ public class ChatMessageShareButton: ASDisplayNode { updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) } + if isSummarize { + if self.topIconNode.image != nil, let snapshotView = self.topIconNode.view.snapshotContentTree() { + self.view.addSubview(snapshotView) + + snapshotView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + self.topIconNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25) + } + } + self.topIconNode.image = updatedIconImage self.topIconOffset = updatedIconOffset @@ -322,6 +336,22 @@ public class ChatMessageShareButton: ASDisplayNode { self.backgroundBlurView?.view.isHidden = false } + if isSummarize { + let starsView: StarsView + if let current = self.starsView { + starsView = current + } else { + starsView = StarsView() + self.starsView = starsView + self.view.insertSubview(starsView, belowSubview: self.topIconNode.view) + } + starsView.frame = CGRect(origin: .zero, size: size) + starsView.update(size: size, color: .white) + } else if let starsView = self.starsView { + self.starsView = nil + starsView.removeFromSuperview() + } + return size } @@ -335,3 +365,70 @@ public class ChatMessageShareButton: ASDisplayNode { } } } + +private final class StarsView: UIView { + private let hierarchyTrackingLayer: HierarchyTrackingLayer + private let topStar = SimpleLayer() + private let bottomStar = SimpleLayer() + + override init(frame: CGRect) { + self.hierarchyTrackingLayer = HierarchyTrackingLayer() + + super.init(frame: frame) + + self.clipsToBounds = true + self.layer.addSublayer(self.hierarchyTrackingLayer) + + self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in + guard let self else { + return + } + self.updateAnimations() + } + + self.layer.addSublayer(self.topStar) + self.layer.addSublayer(self.bottomStar) + + let image = UIImage(bundleImageName: "Settings/Storage/ParticleStar") + self.topStar.contents = image?.cgImage + self.bottomStar.contents = image?.cgImage + + self.topStar.bounds = CGRect(origin: .zero, size: CGSize(width: 10.0, height: 10.0)) + self.bottomStar.bounds = CGRect(origin: .zero, size: CGSize(width: 10.0, height: 10.0)) + + self.topStar.opacity = 0.5 + self.bottomStar.opacity = 0.5 + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func updateAnimations() { + let topAnimation = CAKeyframeAnimation(keyPath: "transform.scale") + topAnimation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.55 as NSNumber] + topAnimation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 1.0 as NSNumber] + topAnimation.duration = 0.9 + topAnimation.autoreverses = true + topAnimation.repeatCount = Float.infinity + topAnimation.beginTime = CACurrentMediaTime() + self.topStar.add(topAnimation, forKey: "blink") + + let bottomAnimation = CAKeyframeAnimation(keyPath: "transform.scale") + bottomAnimation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.55 as NSNumber] + bottomAnimation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 1.0 as NSNumber] + bottomAnimation.duration = 0.9 + bottomAnimation.autoreverses = true + bottomAnimation.repeatCount = Float.infinity + bottomAnimation.beginTime = CACurrentMediaTime() + 0.9 + self.bottomStar.add(bottomAnimation, forKey: "blink") + } + + func update(size: CGSize, color: UIColor) { + self.topStar.layerTintColor = color.cgColor + self.bottomStar.layerTintColor = color.cgColor + + self.topStar.position = CGPoint(x: 9.0, y: 9.0) + self.bottomStar.position = CGPoint(x: size.width - 9.0, y: size.height - 9.0) + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 22ff90dbb6..cec618b143 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -116,6 +116,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private var appliedExpandedBlockIds: Set? private var displayContentsUnderSpoilers: (value: Bool, location: CGPoint?) = (false, nil) + private var isSummaryApplied = false + private final class TextRevealAnimationState { let fromCount: Int let toCount: Int @@ -404,6 +406,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } + var isSummaryApplied = false var isTranslating = false if let invoice { rawText = invoice.description @@ -450,7 +453,13 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } else if let translateToLanguage, !item.message.text.isEmpty && incoming { isTranslating = true for attribute in item.message.attributes { - if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { + if translateToLanguage == "sum", let attribute = attribute as? SummarizationMessageAttribute, let summary = attribute.summary { + rawText = summary.text + messageEntities = summary.entities + isTranslating = false + isSummaryApplied = true + break + } else if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { rawText = attribute.text messageEntities = attribute.entities isTranslating = false @@ -794,6 +803,20 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.textNode.textNode.displaysAsynchronously = !item.presentationData.isPreview animation.animator.updateFrame(layer: strongSelf.containerNode.layer, frame: CGRect(origin: CGPoint(), size: boundingSize), completion: nil) + + if strongSelf.isSummaryApplied != isSummaryApplied { + strongSelf.isSummaryApplied = isSummaryApplied + itemApply?.setInvertOffsetDirection() + + if let snapshotView = strongSelf.textNode.textNode.view.snapshotContentTree() { + strongSelf.view.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + strongSelf.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } if strongSelf.appliedExpandedBlockIds != nil && strongSelf.appliedExpandedBlockIds != strongSelf.expandedBlockIds { itemApply?.setInvertOffsetDirection() } diff --git a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift index 7c7012f3ab..468959c742 100644 --- a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift +++ b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift @@ -20,6 +20,7 @@ public final class GlassBarButtonComponent: Component { public let animateScale: Bool public let component: AnyComponentWithIdentity public let action: ((UIView) -> Void)? + public let tag: AnyObject? public init( size: CGSize?, @@ -29,7 +30,8 @@ public final class GlassBarButtonComponent: Component { isEnabled: Bool = true, animateScale: Bool = true, component: AnyComponentWithIdentity, - action: ((UIView) -> Void)? + action: ((UIView) -> Void)?, + tag: AnyObject? = nil ) { self.size = size self.backgroundColor = backgroundColor @@ -39,6 +41,7 @@ public final class GlassBarButtonComponent: Component { self.animateScale = animateScale self.component = component self.action = action + self.tag = tag } public static func ==(lhs: GlassBarButtonComponent, rhs: GlassBarButtonComponent) -> Bool { @@ -63,10 +66,23 @@ public final class GlassBarButtonComponent: Component { if lhs.component != rhs.component { return false } + if lhs.tag !== rhs.tag { + return false + } return true } - public final class View: UIView { + public final class View: UIView, ComponentTaggedView { + public func matches(tag: Any) -> Bool { + if let component = self.component, let componentTag = component.tag { + let tag = tag as AnyObject + if componentTag === tag { + return true + } + } + return false + } + private let containerView: HighlightTrackingButton private let genericContainerView: UIView private let genericBackgroundView: SimpleGlassView diff --git a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift index aeb082fc5d..2ff9c80220 100644 --- a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift @@ -32,6 +32,7 @@ public final class SearchInputPanelComponent: Component { public let safeInsets: UIEdgeInsets public let placeholder: String? public let resetText: ResetText? + public let hasEdgeEffect: Bool public let updated: ((String) -> Void) public let cancel: () -> Void @@ -42,6 +43,7 @@ public final class SearchInputPanelComponent: Component { safeInsets: UIEdgeInsets, placeholder: String? = nil, resetText: ResetText? = nil, + hasEdgeEffect: Bool = true, updated: @escaping ((String) -> Void), cancel: @escaping () -> Void ) { @@ -51,6 +53,7 @@ public final class SearchInputPanelComponent: Component { self.safeInsets = safeInsets self.placeholder = placeholder self.resetText = resetText + self.hasEdgeEffect = hasEdgeEffect self.updated = updated self.cancel = cancel } @@ -321,6 +324,7 @@ public final class SearchInputPanelComponent: Component { let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight + 30.0), size: CGSize(width: size.width, height: edgeEffectHeight)) transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) self.edgeEffectView.update(content: edgeColor, blur: true, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition) + self.edgeEffectView.isHidden = !component.hasEdgeEffect transition.setFrame(view: self.containerView, frame: CGRect(origin: .zero, size: size)) self.containerView.update(size: size, isDark: component.theme.overallDarkAppearance, transition: transition) diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift index 0a8988b6c7..f6807785f5 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift @@ -537,7 +537,7 @@ final class ChatbotSetupScreenComponent: Component { .init(title: environment.strings.Common_Cancel, action: { completion(false) }), - .init(title: environment.strings.Common_Cancel, type: .default, action: { + .init(title: environment.strings.ChatbotSetup_Gift_Warning_Proceed, type: .default, action: { completion(true) }), ] diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/Contents.json index bfad7e17ec..eac666dc71 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_left.pdf", + "filename" : "browser_back_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/browser_back_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/browser_back_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..16eb92b04b573bce563569a3e30610e53daeb270 GIT binary patch literal 4189 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf deleted file mode 100644 index e77136afef578ff7cb34fa8b636b8b65330e7c12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3911 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

L zniHuz(P}q#w5j>hm#VS>7UtG*ifC^CzG+~RyiL>SSLv%8V) zbWFNk_UapKddHBGS0o0hj>+?3}F_9*R(aS#oRn3!Rx=EvNizm~_@ zJF2VC5L$I7w^gd1>BZJYfH9$)l=Z0f#pQj^#l>{^UhwV5{@oDxzx=P}fG{>T+7bu7 zEzA=NZ8VWcqEY}apKrS05qTO1Xk)N}-~GV}7l6lFlFk5LfDT;eOZ>gi-9mq_zMtfV zqmaVL{x}N2R~Wu~@O8wBM5N+~yc_xc@*QHZWF7X&wqdV2a`2;{Jf F&0n1gYytoP diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Contents.json index 09b651c240..6193eeb8ba 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Bookmark.pdf", + "filename" : "browser_bookmark_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/browser_bookmark_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/browser_bookmark_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3640d0baba96494f4d2a128ca4e05bc642e1c08e GIT binary patch literal 5104 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Browser.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Browser.pdf deleted file mode 100644 index 81f7ac317102df77bcaf0b495208ee9ccf7db77b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2160 zcmZXVdpMNo8^@8unEaLua>~A}^P!nx4C6PLF^tnxV+c8A@RHNanwd0{a%gCsjo7Ib z5!oc=5Zkhably%vE4EaFkknu)Np^;4+r9sIujlz*&wW4l_kOSI`52I$i5AGMHc%)M zKmhcx7$^W>v4DdE6r@Fnw@}M}>TqIo91COsaAF*l1v-P_^axOtAqqGNq(!mz09K;o z6pj}tv*jJjGwhkKX$XaAgjMvaO(BAXR+2dLk zqaWGrPmNMg?e-ly{QE=4KmCs1Gd!Kxe4%=$?yq<*EN6JC6w~r(2#-J1U$lr;*9PM^ zG`)|Q94+XTNkFh~Ucv{C^>nmifZtATQ$9Mhr@^?b;d#$ZE=CsfmI+QMWnz!CtPWS*V%lqCO~D z2cr=}R4XfCHWy`Be3U&BaM|$4xY2iL<0}=upnnR>wr6}}_#eJ}9?CN<_vNDjkA(BZ zktJQ(=X#8Y9xaBgFyFTuxE}MqNJa^Bz`-wIYkpzKoVqbvchhfE27c-+{#5rHkSTZQ#ueGh zCUCUMsYlmP|CJ}!YbH#(agq1`iU6*p^F_PteA(?VXFoZae4^qbRq|6;&s4G!gokn( z2uAw^^O}9Ecq18gOEsvQwucok66uzU9yn#Q!)EG^9QVHBf~xa#D&^J7JBx`V8H3I+ z3S{2eUZe54vnpd2<4p^_t0OIli-^19)?9X*a+H_RV))`}QPbcdmo$hoAJo?^3+0;H znco2m^)@B+I?Xz#gD~Aobz9_iPpI(k119Fv-&ZlS%VopT)TCdQW}o`NsU(0+Dhbz4 zneT}mnNO&HeULl<*s=WyNsxQcs>c!~Klm`Bj`8O7mM-GfZM!+jaU^^B(m#|FR4Yib z0jg7(r~C!Yfshb2yI$q49*v)qW`P5a7Kwbt8d~XynwK#5^)18JgcA17)>~=pkE~s- zfD(D4CLF2iCVSca+Y%i?{8(jto0j{-5Y*KP+)qE`Q3&j7@x9@{ld`wsxnT@vao|l_2t#A^=>c!vK*Vz5E z;C7_F)I*nEwp!L6(iA>XCpN%U)y(cyYEa=Zp9%-pT3Dvy3Hzcv-^Y_&bIm&z`OrE6 zEVJ#FUQs};u}s5}dNqFK*z)s>c#WLRrU>aQV2c7=6cb0|dT=qh+ifAF!)d@`AnYUB;SVHYJ{q%qSR`FI&^*wQ!)ko%m=k$E?eI5kR?bC_6bgtn3$iuA)07X zfwLy_Zu0?`@-GmD1QhuP)N1dL@_JlEX0%M zlv?#VIw6v+l6c*AEoJ$Tw)aw8uEf<;bB7nBpOPnJ&OlO@SqJPt*|u*W$dp3FaQOVX zuVF&`?yuqliNYY)>4938MO1;~X*4>E2?PdzW`+;vdk}z=sZnbX*#<^LQ=RB+AP_)o zMXbb^h1I(BWwlm`0y61)8Q~xk5OYinATg5&bQ%k!iEb1_Y)vFn86pj^{w)0ibz*59 zfpE|02vMtm*Z~DtyN5vEE6PRwr+?-u^46w-{;HDLAangfabv5-2~-v}jvn=ODw_dD zLJ@!sbk)B23!ts7t*il&{5g;aU}Xi+)+H-ZpW;}TkZ1(vKa!Pb5MQ6NMTlJgOS1Wr z$YM~V<3I*f{P*0QMdL7L(&IrBz&JX>WOXQT3Z2dZ#D-S}4EK)S4~n{9;XjqZTCsz~ NSlL1i3|w{){|5EXWUT-I diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Contents.json index c45b00a1de..2a075937c7 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Browser.pdf", + "filename" : "browser_safari_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/browser_safari_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/browser_safari_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6ed6502e7c78f47f30b8ff5f5edd459107a649ea GIT binary patch literal 4956 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf deleted file mode 100644 index c0246a0829413f091daeb41081506023cc17c250..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3914 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/docviewer_24.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/docviewer_24.pdf deleted file mode 100644 index 18bc4e9756bb343f1215c3d5d286610d4a06df27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3269 zcmZXXc{r4N8^^6NV~H$D$umU5SjN7WWo#qaB3a5XgApb(qY&ALBH0TmTgaAu3k_yY z6lHJ7P7%qPc&T36&NkUfL(b|bn23VYWr_oTfF1R@@T z@&fzOr=%IjZ6LTJzxdv2p)-Ef&L3~fXb26}UJwRBd~UQHeuaGMwJ7Y-Uv@q%LH51T zmoKNkew(=TdLsDic2u2k@b$oF!Pmd-Y|U}suX~*ltQA=IU( z^2q-@tdG`s){)EpaQ)e!;N5NQUOQ7~LTSpp2KybSO|Lwe@VpUcA#I3XseQW4>`kq* z;#WDG9h$4#QxwoGN5YMWzDdo|Xe0J=J#)cR)Vg`cPg;Ihe3d0b5e)J!y;}L)r6Q(t zuNF917_LshRW9RfklG#Iv!n81KBmVi+)hMHzhHfwJ{Rzs*|2G%EK@%4wscfo{QWz} zBt+L845a9f^rrL$7?-rzYhcLtmhCbF+Zhrq96aT6=%6~oCg{&&BNDl8B^u+Z<#7TT z2=rkqq^Z-L$B~`WO?^x!-gvaOQ_Mfr6jXv%Q1j@LVP~1j_uWXIkh>zq-6px8les1x zkDg*87`9eq2$jLa$WH}E=A(sVC<8$Vy4EB+83dDi93u4W(+iTId!7b%;&-K<^q@lN zmAT9;1-e~p$8rPU^dTl>`q}c2SxO#vRo3S_VF8UXTGKGO%MB{3k|9=c$CdO>LVM3w zw3qD&G#CwHnJ~x26pvJjib~RwVtT@8oGr@$J91S>O<=0*mBgE;l^tX!eiFhzUQfBE!0_S{FZ%(S{Co2LGF1=$p`3wmuh52|C_qrEk$Kk z2BPnUkGXx$ZS0MPRT;Y1=+6CR{95BiPO}t?X7}U1nWNliugaD;b~KEI)u9vX>9bi1 zbs!v-hSj)Y8{NZ-T(p>i0RofTEGX?ce~M7R_$trR(WK51ipd8@Cn}T0^VpAQqgq=9KS8WcBwOAl1!q^+a?|+6 zjQkQD2X-t81*785S}K*SA1yKQ{_Xu+6SiF6vbdQ)mXJZWcBk?4=?}^H!YylBq=eg; zJ9BVTm>C*%Htg$PN|n-1*6On1EMxYx;p-g{y=eujm&W_T`L4__4wEB-3?w=ZB||t6 z!LSWeoow#6+7)-h;_fpV9p?;vP{lIE07(=Uq-jwSycQd_EBP1)jf&{RdAlp&PT1Ka zYOxGk40T*c-cv6)GK?$OHGs8SG|KH=l&kB0((Y5IJHOj9Y0R@(pQ>m zahP@&2pQC6%Ttm_heRUyd8;p9G#yYI^1UnYR^q$(I`wJprAN1hUpQ*9o3RFy^M&%d zm`6RY`qX&xUw^|(XVi+yN$4T_wK5-G*zg*OE{{o6pLafZJAG8GbeFSHj381ZpBHlkcnt-e4%Ym3V|T3v$I(%SSnc6a>6(J&wM;kD54L)rE{x!rM~%o2JLT#? zlI))>BDe(mr?-5s@C-n!!v~Js2SH~757=i&2bN7{8wmJkH;zVhAelEQTEG1bl$=6iJkaPdVTZFi9SI)r)v-rHV0lIUGw z!MBBVWgn}MA1Xgc#px3H{rglGx=G5OnRK%2In+aVW%hVgRn`l(ghiNB;5^B_N=aBR zue;A55(VaHHFdkdlAGC#l~aVV>4$yrG1@3O&BVlw7_MJ9v2)*7Ma#{im6`^ANjm>- zN_jvuiJ}zPmFeaxvTLeO5|^t#$)2zHN#VFla07))Q*({^YAk$oX`{@^nJZFU#lku1 z%v6rD*&h-)UPL9y%aof7Dr1HoT(HicM*P1s$!S-=bakAH-x(@_=P&$g% z#=pIE@`Uv4m2nljRC+0K8GosqMXb$kCB_FjPCeRW1!#dlULS}GBwOzmZJWa7iuH@k zj0AugVm`y)#oC!?$2OyxwN&S+5q>D3DhP@B#1?TnNu;4O!ywPO`YdBC<&Yj}4(ZC^ z-MW6&Sj<%5#G2jIi49!G?r6id>ZLD-TvPHSRh0S=JJ2sV3BIm@;=a()>Jt+Mg~^z;J`u6dh(tc> z+Oj@g|5xF&mHygo$xl}(>NdH&*yLUm3Zd#1i$Y6gfyY36$rBs1&yS0|r2#*z2cA!%kaL)e$%vd9q+ovcbEu|F!|^+ajpeXDD^fz#qI z>@&X$hr{qo`^5PR8Gp0qK1}{(Qh8-K{11{U{*m`kRaVE_8%HDn_Kv?m^ql_{3?OTc za{EoDrWiC9rHk_e>;bq8{73wKQ1~PLefnL=5<|fG;$1KVU>}b6sdgW9^>E%qjQ3v0 zeKP(nnxpW08ldz``UgMnOMkdp)&z^*8x`1hU!FA!Pn_GoQ~mH5S1=q<0spkW{{)nkloXVJJ^5G2!GRwq!26G+us7#^ z{E_5U06!GHiB zU^}>A0AR}&0H@>1V{lkB9s|JXGmbFn49o4h=ob3m44J|`^w$;L)@RV!jttR`DT8&0 z=L`@?Fj<6{0}#5>(!y|g90tt|BZ$8p$)ql9R;plo4u@(=O71NyJ!uvxk$Y0}@^OFH zukI%vUO`-fxWka^q3f>2^ z#_M~sU*63q^7H)=-})}$d(wo&d6twuo|7u~F!`YUPA`2&Mb#1MSyh3EN4_$%L$+#VBjBp_Y9d}vxl+O{ zGt+INJYnK+)f2~rp>s2U#M#<|nm6<>A}jQ&SxdC_&Ckqy=C4#I^~hh+)nCeeX5T%P z(Ln8am{s>(?#avoWKL--HA&p41z9cJ95QnM}Qz=an6$z?FL{Rrbx0(YrvMqo^=z<*ZwhEi)HsKcpg7 zGL1Uh^xj<#sUxK;6e@&jFRZh#Ten0eoVGM`mP%P9e&MjcR0mbCXqiGTQUN%(g>yPn@?6VTg+_oqZXE)sITqc{#4W{r%?m^M@42Hl93Qe=nLAk9Red zUb{T}L{<4k2N%#Rx;k32X31MsrI6kCm(o=KI@V<5y6VW*ym{Vot*1Uc7^ZE{c}iJk zE`R7G_rA<)3I)U}c+T_0cuKxFr&8{FFZ@f3K&4ng3xj*`uEV5%UBBw+v2pJB9G!rJ z+U~2Q7YKrG2d&bTPYQJp!5c=e+^`_@6!rntVU@?moHI?@#il++K6)H&*X7uWwUIycU8dx8E@({I4` zh@0o>)&1RY_3lbi_1Ef>uyvIxmEM)vk@oBCH~3lqj2yo*L+3s zw(mlQbJmjd=wzF8fn|T}>-+`gRn)5er_(N8=b83|HLPD`(3p~&ZuQH|>sHz;kw zbz^Jn1x}q(@;2pGYBuzeM__2&(x3i3Emlo!cKUm} zZ#oYqKR3vBd+hoxFWBvKv&E;#mQR26=6-khd=Qt*{wvYF&hwN_q6#cFAu4^Zo<=X(pyory_4@ zo`@R5wci=)Bm^?b7@7gVdDVQ}7mM>2RIj@1>YVDp$UPcs<6`e;t5)Y3}~g z{YcSghw0x)QW)O2=~6~S?4_0{r^v{twPv0>UL|U+!OUxP(pJ7s*!Pgwy`8hmL8BR~ zT*Z5wSvh`wQ6wMU5AKil~k-tZ5J1T-fgE?VDhmu%7K4Ja|@ZbNyzS z&EB4xEqwPEFMJOC{5s?M@rL8IC)&z-x7kckhs!_q<+qm@3>yTG92}7wnbXH_^-i|Q z5!m&=FHQX(Yw~{0w>9jM>}J2LXL&=FlCLGJB$>@&xz7W~WJi{b+a^%8sGWQ3ZLV1q zTp#$c@@3M`^1fh|v(C%aDFZ#}9q&svyvlg%QvRWV-b9b7NN5ad92&12&$(e;l6QHy z+N0B>=XA%ycK%#Ge;vtOsj>WP*x2b^^=Yh$?ytKJOQj@bSfu835Gk6-kJs;_6J=|q z<9jw$Z)yD!avv9A_U!7oQ~FW2K`Rs8kXxkO+n?hn3U0vf@~+xz9^3lz#?u2H2fq1O z$mXcsp%0f8&VG7{c`1Bx7w+tlte&N%iXJ_zk%7LK)sj)!2l@***D6xpmF>h zjY~yOieGZde;N*&6~&p|ZT|86+2L2MKGmJpy>1n)ZoVPkH;uKA;a>I^a^GOT_k{*9 zqzJ zwzW?bBa1-N@V+6h;p{T93;XQd+rF(NRpO6PyT6NKM!v0hrWHS0Io>p=KK^ttCbKrb zmSJyNddpXSbj%%Q92wrM;BbG~ss2-z>Zec2@SMYii?^LsGgf-acycx(Hh#nWdc6jU zt=1)-sF;X}xSglB%4t_0k+53f6S5K$>sRBQi|BMD%J}W^TvFh8;Bdtq8yAv94O4>Z zO_1*Ag4XWqe-t73V?dTGgcRD0Anys&Aef*|CxLPm}{cr z_&|-GQ1he|l$O{t<6cy9b8}2ab2F||gIX@Z+woH-RIXkP!(cgzCPH*2oH{WC@%TA8 zK2F1s7~T)yjP>+%Xb#0Xw0YNoAr~N;tdfv0d4mUG6kGV{(+$0|B8GOLPns zPNLH~q<`p$MpsxQI6bf8P-VSc7~v0{i}ApKSp4Jz;HMXw;+1%hh$VsuLLdMnES^H4 zz+`~#h7pVXd%>l%Fcd5V;qhb&K)_>(2uJ`yzzqWuun2@FAp{W$QYZijV__H%l3?MA z41+KU|2sy&f-nd|5J1O>B#8(B;;|qJp%76W5*8srBp45%2NH;Q5ZxqV$z(D_Bmyv& zLLtKl3Drft0AUyn0sv8WD0m_na20~6Yj_H(g9XVDx}5e4M6-v5i4=%9>8eN-FFb;P z9)bFTU@1fb1x7@~p@*Olgj&I)xe;;+63{p(SOkGU7)|GIst^{&LnIJJ=_G@YkWN=2 zGayU`QU8co5=4flf`lLtf?)u{QXmkIS`m{vg^+}iL0B>YA(6-;DHNMZ2uK0QSdSD`TotBDUd7-kw_*J@d(O;&=^__r0Mz*yCO!=JP@#W5F%5^A~Cd3(EKCl zLm`N45l{#%+^I7Eoq*raDd$LNHBQ+>#4$iZGon)d6_$_!CkzW?f%%UwU-->UT|6m7 zMErS@#pvX9l*MB6xWIlp(Y?~@s5^QyrP2;fUp>tjj!c>^TLA3;l|6CBp#NA+Te4(u z*?bP2!3Bg(#8PBmkImvSSm=quE-?*KX&e*-C?e>$ULu5k-JMOCj;O1EFalwdnz}XX zvH7Sj_%HvtDDs;n^aJ_Lk~Ndz$>2~q3?~M9w9rW*8Tw4FyDROeKAUdEwOV~dsrmqFvuorNn1 z{||AoaZ?)W5U`%X675cAfGcKX(&}Vp^k_VqEBnwtV-uipV9<{WkD012;TwQSXg>m+ zQa-uEn#KrZJlbFW#0dW(2cym84>`)e?LlO;*Zd&|k zW4bap7-3e7_0gQKC%~XBo=hN<@R0f+|0XK|XUS&s0AZFVs|vS^>BSH-f-=nI(Kx(G Qj)(+2f?2c1V5i~#0dOV6YXATM literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/Contents.json new file mode 100644 index 0000000000..e907876996 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "browser_share_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/browser_share_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/browser_share_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..23c7c6d41c4f5b7115e697640491212159dfd6c2 GIT binary patch literal 5208 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