mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Add support for web app header and background color customization
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user