From 690045cf9933cf3dcd642fedf7276c164bd8440b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 26 May 2022 01:06:19 +0400 Subject: [PATCH] Add support for web app header and background color customization --- .../ChatListFilterPresetListController.swift | 18 +++---- .../ChatListFilterPresetListItem.swift | 5 +- .../Sources/SolidRoundedButtonComponent.swift | 1 + .../PremiumUI/Sources/PremiumDemoScreen.swift | 11 +++-- .../Sources/PremiumIntroScreen.swift | 25 ++++++++-- .../Sources/ReactionsCarouselComponent.swift | 12 +++-- .../WebUI/Sources/WebAppController.swift | 49 ++++++++++++++++++- 7 files changed, 98 insertions(+), 23 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index c16f0865e3..acc88993e1 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -74,7 +74,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { case suggestedPreset(index: PresetIndex, title: String, label: String, preset: ChatListFilterData) case suggestedAddCustom(String) case listHeader(String) - case preset(index: PresetIndex, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool, isAllChats: Bool) + case preset(index: PresetIndex, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool, isAllChats: Bool, isDisabled: Bool) case addItem(text: String, isEditing: Bool) case listFooter(String) @@ -95,7 +95,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { return 0 case .listHeader: return 100 - case let .preset(index, _, _, _, _, _, _, _): + case let .preset(index, _, _, _, _, _, _, _, _): return 101 + index.value case .addItem: return 1000 @@ -122,7 +122,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { return .suggestedAddCustom case .listHeader: return .listHeader - case let .preset(_, _, _, preset, _, _, _, _): + case let .preset(_, _, _, preset, _, _, _, _, _): return .preset(preset.id) case .addItem: return .addItem @@ -152,8 +152,8 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { }) case let .listHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) - case let .preset(_, title, label, preset, canBeReordered, canBeDeleted, isEditing, isAllChats): - return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title, label: label, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, sectionId: self.section, action: { + case let .preset(_, title, label, preset, canBeReordered, canBeDeleted, isEditing, isAllChats, isDisabled): + return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title, label: label, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: { arguments.openPreset(preset) }, setItemWithRevealedOptions: { lhs, rhs in arguments.setItemWithRevealedOptions(lhs, rhs) @@ -219,10 +219,10 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) { if isPremium, case .allChats = filter { - entries.append(.preset(index: PresetIndex(value: entries.count), title: "", label: "", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: false, isEditing: state.isEditing, isAllChats: true)) + entries.append(.preset(index: PresetIndex(value: entries.count), title: "", label: "", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: false, isEditing: state.isEditing, isAllChats: true, isDisabled: false)) } if case let .filter(_, title, _, _) = filter { - entries.append(.preset(index: PresetIndex(value: entries.count), title: title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing, isAllChats: false)) + entries.append(.preset(index: PresetIndex(value: entries.count), title: title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing, isAllChats: false, isDisabled: false)) } } if actualFilters.count < limits.maxFoldersCount { @@ -528,7 +528,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch } controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ChatListFilterPresetListEntry]) -> Signal in let fromEntry = entries[fromIndex] - guard case let .preset(_, _, _, fromPreset, _, _, _, _) = fromEntry else { + guard case let .preset(_, _, _, fromPreset, _, _, _, _, _) = fromEntry else { return .single(false) } var referenceFilter: ChatListFilter? @@ -536,7 +536,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch var afterAll = false if toIndex < entries.count { switch entries[toIndex] { - case let .preset(_, _, _, preset, _, _, _, _): + case let .preset(_, _, _, preset, _, _, _, _, _): referenceFilter = preset default: if entries[toIndex] < fromEntry { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift index 9e8ba9da0c..1489d68b6d 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift @@ -24,6 +24,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { let canBeReordered: Bool let canBeDeleted: Bool let isAllChats: Bool + let isDisabled: Bool let sectionId: ItemListSectionId let action: () -> Void let setItemWithRevealedOptions: (Int32?, Int32?) -> Void @@ -38,6 +39,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { canBeReordered: Bool, canBeDeleted: Bool, isAllChats: Bool, + isDisabled: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, @@ -51,6 +53,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { self.canBeReordered = canBeReordered self.canBeDeleted = canBeDeleted self.isAllChats = isAllChats + self.isDisabled = isDisabled self.sectionId = sectionId self.action = action self.setItemWithRevealedOptions = setItemWithRevealedOptions @@ -380,7 +383,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN if let arrowImage = strongSelf.arrowNode.image { strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width + revealOffset, y: floorToScreenPixels((layout.contentSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) } - strongSelf.arrowNode.isHidden = item.isAllChats + strongSelf.arrowNode.isHidden = item.isAllChats || item.isDisabled strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 0.0), size: CGSize(width: params.width - params.rightInset - 56.0 - (leftInset + revealOffset + editingOffset), height: layout.contentSize.height)) diff --git a/submodules/Components/SolidRoundedButtonComponent/Sources/SolidRoundedButtonComponent.swift b/submodules/Components/SolidRoundedButtonComponent/Sources/SolidRoundedButtonComponent.swift index 13137aa79d..0706a81724 100644 --- a/submodules/Components/SolidRoundedButtonComponent/Sources/SolidRoundedButtonComponent.swift +++ b/submodules/Components/SolidRoundedButtonComponent/Sources/SolidRoundedButtonComponent.swift @@ -116,6 +116,7 @@ public final class SolidRoundedButtonComponent: Component { } if let button = self.button { + button.title = component.title button.updateTheme(component.theme) let height = button.updateLayout(width: availableSize.width, transition: .immediate) transition.setFrame(view: button, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)), completion: nil) diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 6f73951d60..eaec2901f2 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -129,15 +129,20 @@ private final class GradientBackgroundComponent: Component { final class DemoPageEnvironment: Equatable { public let isDisplaying: Bool + public let isCentral: Bool - public init(isDisplaying: Bool) { + public init(isDisplaying: Bool, isCentral: Bool) { self.isDisplaying = isDisplaying + self.isCentral = isCentral } public static func ==(lhs: DemoPageEnvironment, rhs: DemoPageEnvironment) -> Bool { if lhs.isDisplaying != rhs.isDisplaying { return false } + if lhs.isCentral != rhs.isCentral { + return false + } return true } } @@ -317,7 +322,7 @@ private final class DemoPagerComponent: Component { if let itemView = self.itemViews[item.content.id] { let isDisplaying = itemView.frame.intersects(self.scrollView.bounds) - let environment = DemoPageEnvironment(isDisplaying: isDisplaying) + let environment = DemoPageEnvironment(isDisplaying: isDisplaying, isCentral: isDisplaying) let _ = itemView.update( transition: .immediate, component: item.content.component, @@ -362,7 +367,7 @@ private final class DemoPagerComponent: Component { let itemFrame = CGRect(origin: CGPoint(x: availableSize.width * CGFloat(i), y: 0.0), size: availableSize) let isDisplaying = itemFrame.intersects(self.scrollView.bounds) - let environment = DemoPageEnvironment(isDisplaying: isDisplaying) + let environment = DemoPageEnvironment(isDisplaying: isDisplaying, isCentral: isDisplaying) let _ = itemView.update( transition: itemTransition, component: item.content.component, diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 6e76b9bf09..68212c7c62 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -718,13 +718,15 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment) let context: AccountContext + let isPremium: Bool? let price: String? let present: (ViewController) -> Void let buy: () -> Void let updateIsFocused: (Bool) -> Void - init(context: AccountContext, price: String?, present: @escaping (ViewController) -> Void, buy: @escaping () -> Void, updateIsFocused: @escaping (Bool) -> Void) { + init(context: AccountContext, isPremium: Bool?, price: String?, present: @escaping (ViewController) -> Void, buy: @escaping () -> Void, updateIsFocused: @escaping (Bool) -> Void) { self.context = context + self.isPremium = isPremium self.price = price self.present = present self.buy = buy @@ -735,6 +737,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if lhs.context !== rhs.context { return false } + if lhs.isPremium != rhs.isPremium { + return false + } if lhs.price != rhs.price { return false } @@ -841,7 +846,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let text = text.update( component: MultilineTextComponent( text: .markdown( - text: strings.Premium_Description, + text: context.component.isPremium == true ? strings.Premium_SubscribedDescription : strings.Premium_Description, attributes: markdownAttributes ), horizontalAlignment: .center, @@ -1166,6 +1171,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { var inProgress = false var premiumProduct: InAppPurchaseManager.Product? + var isPremium: Bool? + private var disposable: Disposable? private var paymentDisposable = MetaDisposable() private var activationDisposable = MetaDisposable() @@ -1178,10 +1185,17 @@ private final class PremiumIntroScreenComponent: CombinedComponent { super.init() if let inAppPurchaseManager = context.sharedContext.inAppPurchaseManager { - self.disposable = (inAppPurchaseManager.availableProducts - |> deliverOnMainQueue).start(next: { [weak self] products in + self.disposable = combineLatest( + queue: Queue.mainQueue(), + inAppPurchaseManager.availableProducts, + context.account.postbox.peerView(id: context.account.peerId) + |> map { view -> Bool in + return view.peers[view.peerId]?.isPremium ?? false + } + ).start(next: { [weak self] products, isPremium in if let strongSelf = self { strongSelf.premiumProduct = products.first + strongSelf.isPremium = isPremium strongSelf.updated(transition: .immediate) } }) @@ -1281,7 +1295,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let title = title.update( component: Text( - text: environment.strings.Premium_Title, + text: state.isPremium == true ? environment.strings.Premium_SubscribedTitle : environment.strings.Premium_Title, font: Font.bold(28.0), color: environment.theme.rootController.navigationBar.primaryTextColor ), @@ -1336,6 +1350,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { component: ScrollComponent( content: AnyComponent(PremiumIntroScreenContentComponent( context: context.component.context, + isPremium: state.isPremium, price: state.premiumProduct?.price, present: context.component.present, buy: { [weak state] in diff --git a/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift b/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift index aae0a7d66f..788a7d2f4f 100644 --- a/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift @@ -42,8 +42,12 @@ final class ReactionsCarouselComponent: Component { public final class View: UIView { private var component: ReactionsCarouselComponent? private var node: ReactionCarouselNode? + + private var isVisible = false - public func update(component: ReactionsCarouselComponent, availableSize: CGSize, transition: Transition) -> CGSize { + public func update(component: ReactionsCarouselComponent, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { + let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying + if self.node == nil { let node = ReactionCarouselNode( context: component.context, @@ -54,7 +58,6 @@ final class ReactionsCarouselComponent: Component { self.addSubnode(node) } - let isFirstTime = self.component == nil self.component = component if let node = self.node { @@ -62,9 +65,10 @@ final class ReactionsCarouselComponent: Component { node.updateLayout(size: availableSize, transition: .immediate) } - if isFirstTime { + if isDisplaying && !self.isVisible { self.node?.animateIn() } + self.isVisible = isDisplaying return availableSize } @@ -75,7 +79,7 @@ final class ReactionsCarouselComponent: Component { } public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) + return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition) } } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 8e631ba4a4..b3f9be33c7 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -162,7 +162,7 @@ public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> var secondaryBackgroundColor = presentationTheme.list.blocksBackgroundColor.rgb if backgroundColor == 0x000000 { backgroundColor = presentationTheme.list.itemBlocksBackgroundColor.rgb - secondaryBackgroundColor = presentationTheme.list.plainBackgroundColor.rgb + secondaryBackgroundColor = presentationTheme.list.itemBlocksBackgroundColor.rgb } return [ "bg_color": Int32(bitPattern: backgroundColor), @@ -186,6 +186,9 @@ public final class WebAppController: ViewController, AttachmentContainable { fileprivate class Node: ViewControllerTracingNode, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { private weak var controller: WebAppController? + private let backgroundNode: ASDisplayNode + private let headerBackgroundNode: ASDisplayNode + fileprivate var webView: WebAppWebView? private var placeholderIcon: (UIImage, Bool)? private var placeholderNode: ShimmerEffectNode? @@ -213,6 +216,9 @@ public final class WebAppController: ViewController, AttachmentContainable { self.controller = controller self.presentationData = controller.presentationData + self.backgroundNode = ASDisplayNode() + self.headerBackgroundNode = ASDisplayNode() + super.init() if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 { @@ -244,6 +250,9 @@ public final class WebAppController: ViewController, AttachmentContainable { self.addSubnode(placeholderNode) self.placeholderNode = placeholderNode + self.addSubnode(self.backgroundNode) + self.addSubnode(self.headerBackgroundNode) + let placeholder: Signal<(FileMediaReference, Bool)?, NoError> if durgerKingBotIds.contains(controller.botId.id._internalGetInt64Value()) { placeholder = .single(nil) @@ -471,6 +480,9 @@ public final class WebAppController: ViewController, AttachmentContainable { let previousLayout = self.validLayout?.0 self.validLayout = (layout, navigationBarHeight) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: layout.size)) + transition.updateFrame(node: self.headerBackgroundNode, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight))) + if let webView = self.webView { let frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom))) let viewportFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom))) @@ -669,11 +681,45 @@ public final class WebAppController: ViewController, AttachmentContainable { break } } + case "web_app_set_background_color": + if let json = json, let colorValue = json["color"] as? String, let color = UIColor(hexString: colorValue) { + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear) + transition.updateBackgroundColor(node: self.backgroundNode, color: color) + } + case "web_app_set_header_color": + if let json = json, let colorKey = json["color_key"] as? String, ["bg_color", "secondary_bg_color"].contains(colorKey) { + self.headerColorKey = colorKey + self.updateHeaderBackgroundColor(transition: .animated(duration: 0.2, curve: .linear)) + } default: break } } + private var headerColorKey: String? + private func updateHeaderBackgroundColor(transition: ContainedViewLayoutTransition) { + let color: UIColor? + var backgroundColor = self.presentationData.theme.list.plainBackgroundColor + var secondaryBackgroundColor = self.presentationData.theme.list.blocksBackgroundColor + if backgroundColor.rgb == 0x000000 { + backgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor + secondaryBackgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor + } + if let headerColorKey = self.headerColorKey { + switch headerColorKey { + case "bg_color": + color = backgroundColor + case "secondary_bg_color": + color = secondaryBackgroundColor + default: + color = nil + } + } else { + color = nil + } + transition.updateBackgroundColor(node: self.headerBackgroundNode, color: color ?? .clear) + } + private func handleSendData(data string: String) { guard let controller = self.controller, let buttonText = controller.buttonText, !self.dismissed else { return @@ -702,6 +748,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } else { self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor } + self.updateHeaderBackgroundColor(transition: .immediate) self.sendThemeChangedEvent() }