diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index d79d7080de..c2bcfa24e4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -23,7 +23,6 @@ public final class ThemePreviewController: ViewController { private let previewTheme: PresentationTheme private let source: ThemePreviewSource private let theme = Promise() - private let wallpaper = Promise() private var controllerNode: ThemePreviewControllerNode { return self.displayNode as! ThemePreviewControllerNode @@ -59,15 +58,6 @@ public final class ThemePreviewController: ViewController { themeName = previewTheme.name.string } - if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 { - self.wallpaper.set(cachedWallpaper(account: context.account, slug: file.slug) - |> mapToSignal { wallpaper in - return .single(wallpaper?.wallpaper ?? .color(Int32(bitPattern: previewTheme.chatList.backgroundColor.rgb))) - }) - } else { - self.wallpaper.set(.single(previewTheme.chat.defaultWallpaper)) - } - if let author = previewTheme.author { let titleView = CounterContollerTitleView(theme: self.previewTheme) titleView.title = CounterContollerTitle(title: themeName, counter: author) @@ -123,11 +113,14 @@ public final class ThemePreviewController: ViewController { switch strongSelf.source { case .theme, .slug: - theme = strongSelf.theme.get() - |> take(1) - |> map { theme in + theme = combineLatest(strongSelf.theme.get() |> take(1), strongSelf.controllerNode.wallpaperPromise.get() |> take(1)) + |> map { theme, wallpaper in if let theme = theme { - return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)) + if case let .file(file) = wallpaper, file.id != 0 { + return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)) + } else { + return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)) + } } else { return nil } @@ -175,6 +168,16 @@ public final class ThemePreviewController: ViewController { } }) self.displayNodeDidLoad() + + let previewTheme = self.previewTheme + if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 { + self.controllerNode.wallpaperPromise.set(cachedWallpaper(account: self.context.account, slug: file.slug) + |> mapToSignal { wallpaper in + return .single(wallpaper?.wallpaper ?? .color(Int32(bitPattern: previewTheme.chatList.backgroundColor.rgb))) + }) + } else { + self.controllerNode.wallpaperPromise.set(.single(previewTheme.chat.defaultWallpaper)) + } } private func updateStrings() { diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 535ecd470f..f7cd71c286 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import ChatListUI +import WallpaperResources private func generateMaskImage(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 1.0, height: 60.0), opaque: false, rotatedContext: { size, context in @@ -42,6 +43,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { private var chatNodes: [ListViewItemNode]? private let maskNode: ASImageNode + private let chatContainerNode: ASDisplayNode private let instantChatBackgroundNode: WallpaperBackgroundNode private let remoteChatBackgroundNode: TransformImageNode private var messageNodes: [ListViewItemNode]? @@ -52,6 +54,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { private var wallpaperDisposable: Disposable? private var colorDisposable: Disposable? + private var statusDisposable: Disposable? init(context: AccountContext, previewTheme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping () -> Void) { self.context = context @@ -75,15 +78,25 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.pageControlNode = PageControlNode(dotColor: previewTheme.chatList.unreadBadgeActiveBackgroundColor, inactiveDotColor: previewTheme.list.pageIndicatorInactiveColor) self.chatListBackgroundNode = ASDisplayNode() + + self.chatContainerNode = ASDisplayNode() self.instantChatBackgroundNode = WallpaperBackgroundNode() self.instantChatBackgroundNode.displaysAsynchronously = false self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) self.instantChatBackgroundNode.motionEnabled = previewTheme.chat.defaultWallpaper.settings?.motion ?? false self.remoteChatBackgroundNode = TransformImageNode() + self.remoteChatBackgroundNode.backgroundColor = previewTheme.chatList.backgroundColor self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings) + if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 { + self.remoteChatBackgroundNode.isHidden = false + self.toolbarNode.setDoneEnabled(false) + } else { + self.remoteChatBackgroundNode.isHidden = true + } + self.maskNode = ASImageNode() self.maskNode.displaysAsynchronously = false self.maskNode.displayWithoutProcessing = true @@ -114,8 +127,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.addSubnode(self.toolbarNode) self.scrollNode.addSubnode(self.chatListBackgroundNode) - self.scrollNode.addSubnode(self.instantChatBackgroundNode) - self.scrollNode.addSubnode(self.remoteChatBackgroundNode) + self.scrollNode.addSubnode(self.chatContainerNode) + + self.chatContainerNode.addSubnode(self.instantChatBackgroundNode) + self.chatContainerNode.addSubnode(self.remoteChatBackgroundNode) self.toolbarNode.cancel = { dismiss() @@ -137,10 +152,53 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { } } }) + + self.wallpaperDisposable = (self.wallpaperPromise.get() + |> deliverOnMainQueue).start(next: { [weak self] wallpaper in + guard let strongSelf = self else { + return + } + if case let .file(file) = wallpaper { + let dimensions = file.file.dimensions ?? CGSize(width: 100.0, height: 100.0) + let displaySize = dimensions.dividedByScreenScale().integralFloor + + var convertedRepresentations: [ImageRepresentationWithReference] = [] + for representation in file.file.previewRepresentations { + convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .wallpaper(resource: representation.resource))) + } + convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(resource: file.file.resource))) + + let fileReference = FileMediaReference.standalone(media: file.file) + let signal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: fileReference, representations: convertedRepresentations, alwaysShowThumbnailFirst: true, autoFetchFullSize: false) + strongSelf.remoteChatBackgroundNode.setSignal(signal) + + let account = strongSelf.context.account + let statusSignal = strongSelf.context.sharedContext.accountManager.mediaBox.resourceStatus(file.file.resource) + |> take(1) + |> mapToSignal { status -> Signal in + if case .Local = status { + return .single(status) + } else { + return account.postbox.mediaBox.resourceStatus(file.file.resource) + } + } + + strongSelf.statusDisposable = (statusSignal + |> deliverOnMainQueue).start(next: { [weak self] status in + if let strongSelf = self, case .Local = status { + strongSelf.toolbarNode.setDoneEnabled(true) + } + }) + + strongSelf.remoteChatBackgroundNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))() + } + }) } deinit { self.colorDisposable?.dispose() + self.wallpaperDisposable?.dispose() + self.statusDisposable?.dispose() } override func didLoad() { @@ -305,7 +363,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) itemNode!.isUserInteractionEnabled = false messageNodes.append(itemNode!) - self.instantChatBackgroundNode.addSubnode(itemNode!) + self.chatContainerNode.addSubnode(itemNode!) } self.messageNodes = messageNodes } @@ -326,8 +384,9 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let toolbarHeight = 49.0 + layout.intrinsicInsets.bottom self.chatListBackgroundNode.frame = CGRect(x: bounds.width, y: 0.0, width: bounds.width, height: bounds.height) - self.instantChatBackgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height) - self.remoteChatBackgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height) + self.chatContainerNode.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height) + self.instantChatBackgroundNode.frame = self.chatContainerNode.bounds + self.remoteChatBackgroundNode.frame = self.chatContainerNode.bounds self.scrollNode.view.contentSize = CGSize(width: bounds.width * 2.0, height: bounds.height) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 2e54688f2e..8a51e2d4aa 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -562,7 +562,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let cloudThemes: [PresentationThemeReference] = cloudThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil)) } var availableThemes = defaultThemes - if !defaultThemes.contains(settings.theme) && !cloudThemes.contains(settings.theme) { + if defaultThemes.first(where: { $0.index == settings.theme.index }) == nil && cloudThemes.first(where: { $0.index == settings.theme.index }) == nil { availableThemes.append(settings.theme) } availableThemes.append(contentsOf: cloudThemes) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift index 72f6822ef4..bac5203446 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift @@ -412,7 +412,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { updated = true } - let selected = theme == item.currentTheme + let selected = theme.index == item.currentTheme.index if selected { selectedNode = imageNode } diff --git a/submodules/TelegramCore/TelegramCore/Account.swift b/submodules/TelegramCore/TelegramCore/Account.swift index 28d757fcc7..7792dd34c7 100644 --- a/submodules/TelegramCore/TelegramCore/Account.swift +++ b/submodules/TelegramCore/TelegramCore/Account.swift @@ -1305,6 +1305,7 @@ public class Account { self.managedOperationsDisposable.add(managedPendingPeerNotificationSettings(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedSynchronizeAppLogEventsOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedNotificationSettingsBehaviors(postbox: self.postbox).start()) + self.managedOperationsDisposable.add(managedThemesUpdates(accountManager: accountManager, postbox: self.postbox, network: self.network).start()) if !self.supplementary { self.managedOperationsDisposable.add(managedAnimatedEmojiUpdates(postbox: self.postbox, network: self.network).start()) diff --git a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift index ed73718a1b..af4f8c2a57 100644 --- a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift @@ -2955,6 +2955,14 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP updatedEntries.append(OrderedItemListEntry(id: id, contents: theme)) } transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) + accountManager.transaction { transaction in + transaction.updateSharedData(SharedDataKeys.themeSettings, { current in + if let current = current as? ThemeSettings, let theme = current.currentTheme, let updatedTheme = updatedThemes[theme.id] { + return ThemeSettings(currentTheme: updatedTheme) + } + return current + }) + } } addedIncomingMessageIds.append(contentsOf: addedSecretMessageIds) diff --git a/submodules/TelegramCore/TelegramCore/Themes.swift b/submodules/TelegramCore/TelegramCore/Themes.swift index 3450b719e1..f7ee62972b 100644 --- a/submodules/TelegramCore/TelegramCore/Themes.swift +++ b/submodules/TelegramCore/TelegramCore/Themes.swift @@ -115,18 +115,18 @@ public func getTheme(account: Account, slug: String) -> Signal Signal { +private func checkThemeUpdated(network: Network, theme: TelegramTheme) -> Signal { guard let file = theme.file, let fileId = file.id?.id else { return .fail(.generic) } - return account.network.request(Api.functions.account.getTheme(format: themeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), documentId: fileId)) + return network.request(Api.functions.account.getTheme(format: themeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), documentId: fileId)) |> mapError { _ -> GetThemeError in return .generic } - |> map { theme -> CheckThemeUpdatedResult in + |> map { theme -> ThemeUpdatedResult in if let theme = TelegramTheme(apiTheme: theme) { return .updated(theme) } else { @@ -448,3 +448,48 @@ public func applyTheme(accountManager: AccountManager, account: Account, theme: } |> switchToLatest } + +func managedThemesUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { + return accountManager.sharedData(keys: [SharedDataKeys.themeSettings]) + |> mapToSignal { sharedData -> Signal in + let themeSettings = (sharedData.entries[SharedDataKeys.themeSettings] as? ThemeSettings) ?? ThemeSettings(currentTheme: nil) + if let currentTheme = themeSettings.currentTheme { + let poll = Signal { subscriber in + return checkThemeUpdated(network: network, theme: currentTheme).start(next: { result in + if case let .updated(updatedTheme) = result { + let _ = accountManager.transaction { transaction in + transaction.updateSharedData(SharedDataKeys.themeSettings, { _ in + return ThemeSettings(currentTheme: updatedTheme) + }) + }.start() + let _ = postbox.transaction { transaction in + + } + } + subscriber.putCompletion() + }) + } + return ((.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue())) |> then(poll)) |> restart + } else { + return .complete() + } + } +} + +public func actualizedTheme(accountManager: AccountManager, theme: TelegramTheme) -> Signal { + var currentTheme = theme + return accountManager.sharedData(keys: [SharedDataKeys.themeSettings]) + |> map { sharedData -> TelegramTheme in + let themeSettings = (sharedData.entries[SharedDataKeys.themeSettings] as? ThemeSettings) ?? ThemeSettings(currentTheme: nil) + if let updatedTheme = themeSettings.currentTheme, updatedTheme.id == currentTheme.id { + if updatedTheme != currentTheme { + currentTheme = updatedTheme + return updatedTheme + } else { + return currentTheme + } + } else { + return theme + } + } +}