Add support for web app header and background color customization

This commit is contained in:
Ilya Laktyushin
2022-05-26 01:06:19 +04:00
parent f4d9c0e2f7
commit 690045cf99
7 changed files with 98 additions and 23 deletions

View File

@@ -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<Bool, NoError> 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 {

View File

@@ -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))

View File

@@ -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)

View File

@@ -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,

View File

@@ -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<EnvironmentType>(
content: AnyComponent(PremiumIntroScreenContentComponent(
context: context.component.context,
isPremium: state.isPremium,
price: state.premiumProduct?.price,
present: context.component.present,
buy: { [weak state] in

View File

@@ -43,7 +43,11 @@ final class ReactionsCarouselComponent: Component {
private var component: ReactionsCarouselComponent?
private var node: ReactionCarouselNode?
public func update(component: ReactionsCarouselComponent, availableSize: CGSize, transition: Transition) -> CGSize {
private var isVisible = false
public func update(component: ReactionsCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, 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<DemoPageEnvironment>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}

View File

@@ -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()
}