import Foundation import UIKit import Postbox import TelegramCore import SyncCore import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences import MediaResources import WallpaperResources import AccountContext private final class ThemeUpdateManagerContext { let themeReference: PresentationThemeReference private let disposable: Disposable let isAutoNight: Bool init(themeReference: PresentationThemeReference, disposable: Disposable, isAutoNight: Bool) { self.themeReference = themeReference self.disposable = disposable self.isAutoNight = isAutoNight } deinit { self.disposable.dispose() } } final class ThemeUpdateManagerImpl: ThemeUpdateManager { private let sharedContext: SharedAccountContext private let account: Account private var contexts: [Int64: ThemeUpdateManagerContext] = [:] private let queue = Queue() private var disposable: Disposable? private var currentThemeSettings: PresentationThemeSettings? init(sharedContext: SharedAccountContext, account: Account) { self.sharedContext = sharedContext self.account = account self.disposable = (sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) |> map { sharedData -> PresentationThemeSettings in return (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings } |> deliverOn(queue)).start(next: { [weak self] themeSettings in self?.presentationThemeSettingsUpdated(themeSettings) }) } deinit { self.disposable?.dispose() } private func presentationThemeSettingsUpdated(_ themeSettings: PresentationThemeSettings) { let previousThemeSettings = self.currentThemeSettings self.currentThemeSettings = themeSettings var previousIds = Set() if let previousThemeSettings = previousThemeSettings { previousIds.insert(previousThemeSettings.theme.index) } var validIds = Set() var themes: [Int64: (PresentationThemeReference, Bool)] = [:] if case .cloud = themeSettings.theme { validIds.insert(themeSettings.theme.index) themes[themeSettings.theme.index] = (themeSettings.theme, false) } if case .cloud = themeSettings.automaticThemeSwitchSetting.theme, themeSettings.automaticThemeSwitchSetting.trigger != .explicitNone { validIds.insert(themeSettings.automaticThemeSwitchSetting.theme.index) themes[themeSettings.automaticThemeSwitchSetting.theme.index] = (themeSettings.automaticThemeSwitchSetting.theme, true) } if previousIds != validIds { for id in validIds { if let _ = self.contexts[id] { } else if let (theme, isAutoNight) = themes[id], case let .cloud(info) = theme { var currentTheme = theme let account = self.account let accountManager = self.sharedContext.accountManager let disposable = (actualizedTheme(account: account, accountManager: accountManager, theme: info.theme) |> mapToSignal { theme -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in guard let file = theme.file else { return .complete() } return telegramThemeData(account: account, accountManager: accountManager, reference: .standalone(resource: file.resource)) |> mapToSignal { data -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in guard let data = data, let presentationTheme = makePresentationTheme(data: data) else { return .complete() } let resolvedWallpaper: Signal if case let .file(id, _, _, _, _, _, slug, _, settings) = presentationTheme.chat.defaultWallpaper, id == 0 { resolvedWallpaper = cachedWallpaper(account: account, slug: slug, settings: settings) |> map { wallpaper in return wallpaper?.wallpaper } } else { resolvedWallpaper = .single(nil) } return resolvedWallpaper |> mapToSignal { wallpaper -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in if let wallpaper = wallpaper, case let .file(_, _, _, _, _, _, slug, file, _) = wallpaper { var convertedRepresentations: [ImageRepresentationWithReference] = [] convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.resource, progressiveSizes: []), reference: .wallpaper(wallpaper: .slug(slug), resource: file.resource))) return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) |> mapToSignal { _, fullSizeData, complete -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in guard complete, let fullSizeData = fullSizeData else { return .complete() } accountManager.mediaBox.storeResourceData(file.resource.id, data: fullSizeData, synchronous: true) return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper, creatorAccountId: theme.isCreator ? account.id : nil)), presentationTheme)) } } else { return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: theme.isCreator ? account.id : nil)), presentationTheme)) } } } }).start(next: { updatedTheme, presentationTheme in if updatedTheme != currentTheme { currentTheme = updatedTheme let _ = (accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in let current: PresentationThemeSettings if let entry = entry as? PresentationThemeSettings { current = entry } else { current = PresentationThemeSettings.defaultSettings } var theme = current.theme var automaticThemeSwitchSetting = current.automaticThemeSwitchSetting if isAutoNight { automaticThemeSwitchSetting.theme = updatedTheme } else { theme = updatedTheme } return PresentationThemeSettings(theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) }).start() } }) self.contexts[id] = ThemeUpdateManagerContext(themeReference: theme, disposable: disposable, isAutoNight: isAutoNight) } } for id in previousIds { if !validIds.contains(id) { self.contexts[id] = nil } } } } }