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 0000000000..16eb92b04b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/browser_back_30.pdf differ 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 e77136afef..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Bookmark.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Bookmark.pdf deleted file mode 100644 index d413418c5d..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Bookmark.pdf and /dev/null differ 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 0000000000..3640d0baba Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/browser_bookmark_30.pdf differ 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 81f7ac3171..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Browser.pdf and /dev/null differ 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 0000000000..6ed6502e7c Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/browser_safari_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json index 12dcf45c10..b79b78d930 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_right.pdf", + "filename" : "browser_forward_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/browser_forward_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/browser_forward_30.pdf new file mode 100644 index 0000000000..162c2090b9 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/browser_forward_30.pdf differ 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 c0246a0829..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/Contents.json index c60877c403..223317f393 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "docviewer_24.pdf", + "filename" : "browser_doc_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/browser_doc_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/browser_doc_30.pdf new file mode 100644 index 0000000000..d0d4f47124 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/browser_doc_30.pdf differ 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 18bc4e9756..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/docviewer_24.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/Contents.json new file mode 100644 index 0000000000..17c8b58037 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "browser_search_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/browser_search_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/browser_search_30.pdf new file mode 100644 index 0000000000..4a5636aa33 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/browser_search_30.pdf differ 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 0000000000..23c7c6d41c Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/browser_share_30.pdf differ