diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index bdc257b773..92e97264fc 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -176,12 +176,11 @@ private struct EditThemeControllerState: Equatable { var mode: EditThemeControllerMode var title: String var slug: String - var previewTheme: PresentationTheme var updatedTheme: PresentationTheme? var updating: Bool } -private func editThemeControllerEntries(presentationData: PresentationData, state: EditThemeControllerState) -> [EditThemeControllerEntry] { +private func editThemeControllerEntries(presentationData: PresentationData, state: EditThemeControllerState, previewTheme: PresentationTheme) -> [EditThemeControllerEntry] { var entries: [EditThemeControllerEntry] = [] var isCreate = false @@ -196,7 +195,7 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat } entries.append(.chatPreviewHeader(presentationData.theme, presentationData.strings.EditTheme_Preview.uppercased())) - entries.append(.chatPreview(presentationData.theme, state.previewTheme, state.previewTheme.chat.defaultWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder)) + entries.append(.chatPreview(presentationData.theme, previewTheme, previewTheme.chat.defaultWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder)) let uploadText: String let uploadInfo: String @@ -221,18 +220,30 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat public func editThemeController(context: AccountContext, mode: EditThemeControllerMode, navigateToChat: ((PeerId) -> Void)? = nil) -> ViewController { let initialState: EditThemeControllerState + let previewThemePromise = Promise() switch mode { case .create: let presentationData = context.sharedContext.currentPresentationData.with { $0 } - initialState = EditThemeControllerState(mode: mode, title: "", slug: "", previewTheme: presentationData.theme.withUpdated(name: "", author: nil, defaultWallpaper: presentationData.chatWallpaper), updatedTheme: nil, updating: false) - case let .edit(theme): - let previewTheme: PresentationTheme - if let file = theme.theme.file, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let theme = makePresentationTheme(data: data, resolvedWallpaper: theme.resolvedWallpaper) { - previewTheme = theme + initialState = EditThemeControllerState(mode: mode, title: "", slug: "", updatedTheme: nil, updating: false) + previewThemePromise.set(.single(presentationData.theme.withUpdated(name: "", author: nil, defaultWallpaper: presentationData.chatWallpaper))) + case let .edit(info): + if let file = info.theme.file, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let theme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) { + if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { + previewThemePromise.set(cachedWallpaper(account: context.account, slug: file.slug) + |> map ({ wallpaper -> PresentationTheme in + if let wallpaper = wallpaper { + return theme.withUpdated(name: nil, author: nil, defaultWallpaper: wallpaper.wallpaper) + } else { + return theme.withUpdated(name: nil, author: nil, defaultWallpaper: .color(Int32(bitPattern: theme.chatList.backgroundColor.rgb))) + } + })) + } else { + previewThemePromise.set(.single(theme.withUpdated(name: nil, author: nil, defaultWallpaper: info.resolvedWallpaper))) + } } else { - previewTheme = context.sharedContext.currentPresentationData.with { $0 }.theme + previewThemePromise.set(.single(context.sharedContext.currentPresentationData.with { $0 }.theme)) } - initialState = EditThemeControllerState(mode: mode, title: theme.theme.title, slug: theme.theme.slug, previewTheme: previewTheme, updatedTheme: nil, updating: false) + initialState = EditThemeControllerState(mode: mode, title: info.theme.title, slug: info.theme.slug, updatedTheme: nil, updating: false) } let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -249,22 +260,50 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll }, openFile: { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = legacyICloudFilePicker(theme: presentationData.theme, mode: .import, documentTypes: ["org.telegram.Telegram-iOS.theme"], completion: { urls in - if let url = urls.first, let data = try? Data(contentsOf: url), let theme = makePresentationTheme(data: data) { - updateState { current in - var state = current - state.previewTheme = theme - state.updatedTheme = theme - return state + if let url = urls.first{ + if let data = try? Data(contentsOf: url), let theme = makePresentationTheme(data: data) { + if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { + let _ = (cachedWallpaper(account: context.account, slug: file.slug) + |> mapToSignal { wallpaper -> Signal in + if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper { + var convertedRepresentations: [ImageRepresentationWithReference] = [] + convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .wallpaper(resource: file.file.resource))) + return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) + |> map { _ -> TelegramWallpaper? in + return wallpaper.wallpaper + } + } else { + return .single(nil) + } + } + |> deliverOnMainQueue).start(next: { wallpaper in + let updatedTheme = theme.withUpdated(name: nil, author: nil, defaultWallpaper: wallpaper) + updateState { current in + var state = current + previewThemePromise.set(.single(updatedTheme)) + state.updatedTheme = updatedTheme + return state + } + }) + } else { + updateState { current in + var state = current + previewThemePromise.set(.single(theme)) + state.updatedTheme = theme + return state + } + } + } + else { + presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_FileReadError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } - } else { - presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_FileReadError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } }) presentControllerImpl?(controller, nil) }) - let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get()) - |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, EditThemeControllerEntry.ItemGenerationArguments)) in + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), previewThemePromise.get()) + |> map { presentationData, state, previewTheme -> (ItemListControllerState, (ItemListNodeState, EditThemeControllerEntry.ItemGenerationArguments)) in let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { dismissImpl?() }) @@ -293,75 +332,122 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll return state } - let saveThemeTemplateFile: (String, LocalFileMediaResource, @escaping () -> Void) -> Void = { title, resource, completion in - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: resource.fileId), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: "application/x-tgtheme-ios", size: nil, attributes: [.FileName(fileName: "\(title).tgios-theme")]) - let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil) + let _ = (previewThemePromise.get() + |> deliverOnMainQueue).start(next: { previewTheme in + let saveThemeTemplateFile: (String, LocalFileMediaResource, @escaping () -> Void) -> Void = { title, resource, completion in + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: resource.fileId), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: "application/x-tgtheme-ios", size: nil, attributes: [.FileName(fileName: "\(title).tgios-theme")]) + let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil) - let _ = enqueueMessages(account: context.account, peerId: context.account.peerId, messages: [message]).start() + let _ = enqueueMessages(account: context.account, peerId: context.account.peerId, messages: [message]).start() - if let navigateToChat = navigateToChat { - presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_ThemeTemplateAlert, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_SavedMessages, action: { + if let navigateToChat = navigateToChat { + presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.EditTheme_ThemeTemplateAlert, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_SavedMessages, action: { + completion() + navigateToChat(context.account.peerId) + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + completion() + })], actionLayout: .vertical), nil) + } else { completion() - navigateToChat(context.account.peerId) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - completion() - })], actionLayout: .vertical), nil) - } else { - completion() + } } - } - - let theme: PresentationTheme? - let hasCustomFile: Bool - if let updatedTheme = state.updatedTheme { - theme = updatedTheme.withUpdated(name: state.title, author: "", defaultWallpaper: nil) - hasCustomFile = true - } else { - if case let .edit(info) = mode, let _ = info.theme.file { - theme = nil + + let theme: PresentationTheme? + let hasCustomFile: Bool + if let updatedTheme = state.updatedTheme { + theme = updatedTheme.withUpdated(name: state.title, author: "", defaultWallpaper: nil) hasCustomFile = true } else { - theme = state.previewTheme.withUpdated(name: state.title, author: "", defaultWallpaper: nil) - hasCustomFile = false - } - } - - let themeResource: LocalFileMediaResource? - let themeData: Data? - let themeThumbnailData: Data? - if let theme = theme, let themeString = encodePresentationTheme(theme), let data = themeString.data(using: .utf8) { - let resource = LocalFileMediaResource(fileId: arc4random64()) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) - themeResource = resource - themeData = data - - let themeThumbnail = generateImage(CGSize(width: 213, height: 320.0), contextGenerator: { size, context in - if let image = generateImage(CGSize(width: 194.0, height: 291.0), contextGenerator: { size, c in - drawThemeImage(context: c, theme: theme, size: size) - })?.cgImage { - context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + if case let .edit(info) = mode, let _ = info.theme.file { + theme = nil + hasCustomFile = true + } else { + theme = previewTheme.withUpdated(name: state.title, author: "", defaultWallpaper: nil) + hasCustomFile = false } - }, scale: 1.0) - themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6) - } else { - themeResource = nil - themeData = nil - themeThumbnailData = nil - } - - let resolvedWallpaper: TelegramWallpaper? - if let theme = theme, case let .file(file) = theme.chat.defaultWallpaper, file.id != 0 { - resolvedWallpaper = theme.chat.defaultWallpaper - updateCachedWallpaper(account: context.account, wallpaper: theme.chat.defaultWallpaper) - } else { - resolvedWallpaper = nil - } - - switch mode { - case .create: - if let themeResource = themeResource { - let _ = (createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData) + } + + let themeResource: LocalFileMediaResource? + let themeData: Data? + let themeThumbnailData: Data? + if let theme = theme, let themeString = encodePresentationTheme(theme), let data = themeString.data(using: .utf8) { + let resource = LocalFileMediaResource(fileId: arc4random64()) + context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + themeResource = resource + themeData = data + + var wallpaperImage: UIImage? + if case .file = theme.chat.defaultWallpaper { + wallpaperImage = chatControllerBackgroundImage(theme: theme, wallpaper: theme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: false) + } + let themeThumbnail = generateImage(CGSize(width: 213, height: 320.0), contextGenerator: { size, context in + if let image = generateImage(CGSize(width: 194.0, height: 291.0), contextGenerator: { size, c in + drawThemeImage(context: c, theme: theme, wallpaperImage: wallpaperImage, size: size) + })?.cgImage { + context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + } + }, scale: 1.0) + themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6) + } else { + themeResource = nil + themeData = nil + themeThumbnailData = nil + } + + let resolvedWallpaper: TelegramWallpaper? + if let theme = theme, case let .file(file) = theme.chat.defaultWallpaper, file.id != 0 { + resolvedWallpaper = theme.chat.defaultWallpaper + updateCachedWallpaper(account: context.account, wallpaper: theme.chat.defaultWallpaper) + } else { + resolvedWallpaper = nil + } + + switch mode { + case .create: + if let themeResource = themeResource { + let _ = (createTheme(account: context.account, title: state.title, resource: themeResource, thumbnailData: themeThumbnailData) + |> deliverOnMainQueue).start(next: { next in + if case let .result(resultTheme) = next { + let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() + let _ = (context.sharedContext.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 + } + if let resource = resultTheme.file?.resource, let data = themeData { + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + } + let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper)) + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + if let theme = theme { + themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper + } + return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + }) + } |> deliverOnMainQueue).start(completed: { + if !hasCustomFile { + saveThemeTemplateFile(state.title, themeResource, { + dismissImpl?() + }) + } else { + dismissImpl?() + } + }) + } + }, error: { error in + arguments.updateState { current in + var state = current + state.updating = false + return state + } + }) + } + case let .edit(info): + let _ = (updateTheme(account: context.account, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource) |> deliverOnMainQueue).start(next: { next in if case let .result(resultTheme) = next { let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() @@ -387,7 +473,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) } |> deliverOnMainQueue).start(completed: { - if !hasCustomFile { + if let themeResource = themeResource, !hasCustomFile { saveThemeTemplateFile(state.title, themeResource, { dismissImpl?() }) @@ -403,51 +489,8 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll return state } }) - } - case let .edit(info): - let _ = (updateTheme(account: context.account, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource) - |> deliverOnMainQueue).start(next: { next in - if case let .result(resultTheme) = next { - let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() - let _ = (context.sharedContext.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 - } - - if let resource = resultTheme.file?.resource, let data = themeData { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) - } - - let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper)) - var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - if let theme = theme { - themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper - } - - return PresentationThemeSettings(chatWallpaper: theme?.chat.defaultWallpaper ?? current.chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) - }) - } |> deliverOnMainQueue).start(completed: { - if let themeResource = themeResource, !hasCustomFile { - saveThemeTemplateFile(state.title, themeResource, { - dismissImpl?() - }) - } else { - dismissImpl?() - } - }) - } - }, error: { error in - arguments.updateState { current in - var state = current - state.updating = false - return state - } - }) - } + } + }) }) } @@ -463,7 +506,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll } } let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(entries: editThemeControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: focusItemTag, emptyStateItem: nil, animateChanges: false) + let listState = ItemListNodeState(entries: editThemeControllerEntries(presentationData: presentationData, state: state, previewTheme: previewTheme), style: .blocks, focusItemTag: focusItemTag, emptyStateItem: nil, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 2289268b96..ccba89f347 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -75,7 +75,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate self.scrollNode = ASScrollNode() self.pageControlBackgroundNode = ASDisplayNode() self.pageControlBackgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3) - self.pageControlBackgroundNode.cornerRadius = 6.0 + self.pageControlBackgroundNode.cornerRadius = 8.0 self.pageControlNode = PageControlNode(dotColor: self.theme.chatList.unreadBadgeActiveBackgroundColor, inactiveDotColor: self.presentationData.theme.list.pageIndicatorInactiveColor) @@ -168,10 +168,10 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } }) - self.serviceColorDisposable = (chatServiceBackgroundColor(wallpaper: self.presentationData.theme.chat.defaultWallpaper, mediaBox: context.account.postbox.mediaBox) + self.serviceColorDisposable = (chatServiceBackgroundColor(wallpaper: self.presentationData.chatWallpaper, mediaBox: context.account.postbox.mediaBox) |> deliverOnMainQueue).start(next: { [weak self] color in if let strongSelf = self { - if strongSelf.presentationData.theme.chat.defaultWallpaper.hasWallpaper { + if strongSelf.presentationData.chatWallpaper.hasWallpaper { strongSelf.pageControlBackgroundNode.backgroundColor = color } else { strongSelf.pageControlBackgroundNode.backgroundColor = .clear diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index d1343ca9d6..c2bcfa24e4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -10,6 +10,7 @@ import TelegramUIPreferences import AccountContext import ShareController import CounterContollerTitleView +import WallpaperResources public enum ThemePreviewSource { case theme(TelegramTheme) @@ -112,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 } @@ -164,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 419713d492..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 @@ -30,6 +31,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { private let previewTheme: PresentationTheme private var presentationData: PresentationData + public let wallpaperPromise = Promise() + private let referenceTimestamp: Int32 private let scrollNode: ASScrollNode @@ -40,14 +43,18 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { private var chatNodes: [ListViewItemNode]? private let maskNode: ASImageNode - private let chatBackgroundNode: WallpaperBackgroundNode + private let chatContainerNode: ASDisplayNode + private let instantChatBackgroundNode: WallpaperBackgroundNode + private let remoteChatBackgroundNode: TransformImageNode private var messageNodes: [ListViewItemNode]? private let toolbarNode: WallpaperGalleryToolbarNode private var validLayout: (ContainerViewLayout, CGFloat)? + 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 @@ -63,20 +70,33 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.referenceTimestamp = Int32(calendar.date(from: components)?.timeIntervalSince1970 ?? 0.0) self.scrollNode = ASScrollNode() + self.pageControlBackgroundNode = ASDisplayNode() self.pageControlBackgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3) - self.pageControlBackgroundNode.cornerRadius = 6.0 + self.pageControlBackgroundNode.cornerRadius = 8.0 self.pageControlNode = PageControlNode(dotColor: previewTheme.chatList.unreadBadgeActiveBackgroundColor, inactiveDotColor: previewTheme.list.pageIndicatorInactiveColor) self.chatListBackgroundNode = ASDisplayNode() - self.chatBackgroundNode = WallpaperBackgroundNode() - self.chatBackgroundNode.displaysAsynchronously = false - self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) - self.chatBackgroundNode.motionEnabled = previewTheme.chat.defaultWallpaper.settings?.motion ?? false + + 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 @@ -94,7 +114,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.maskNode.image = generateMaskImage(color: self.previewTheme.chatList.backgroundColor) if case let .color(value) = self.previewTheme.chat.defaultWallpaper { - self.chatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) + self.instantChatBackgroundNode.backgroundColor = UIColor(rgb: UInt32(bitPattern: value)) } self.pageControlNode.isUserInteractionEnabled = false @@ -107,7 +127,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.addSubnode(self.toolbarNode) self.scrollNode.addSubnode(self.chatListBackgroundNode) - self.scrollNode.addSubnode(self.chatBackgroundNode) + self.scrollNode.addSubnode(self.chatContainerNode) + + self.chatContainerNode.addSubnode(self.instantChatBackgroundNode) + self.chatContainerNode.addSubnode(self.remoteChatBackgroundNode) self.toolbarNode.cancel = { dismiss() @@ -116,7 +139,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { apply() } - self.colorDisposable = (chatServiceBackgroundColor(wallpaper: self.previewTheme.chat.defaultWallpaper, mediaBox: context.account.postbox.mediaBox) + self.colorDisposable = (self.wallpaperPromise.get() + |> mapToSignal { wallpaper in + return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.account.postbox.mediaBox) + } |> deliverOnMainQueue).start(next: { [weak self] color in if let strongSelf = self { if strongSelf.previewTheme.chat.defaultWallpaper.hasWallpaper { @@ -126,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() { @@ -294,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.chatBackgroundNode.addSubnode(itemNode!) + self.chatContainerNode.addSubnode(itemNode!) } self.messageNodes = messageNodes } @@ -315,7 +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.chatBackgroundNode.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/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index 3cf4de2a2d..411c10018d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -124,9 +124,6 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: item.strings.Appearance_PreviewReplyText, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - //let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: item.componentTheme, wallpaper: item.wallpaper), fontSize: item.fontSize, strings: item.strings, dateTimeFormat: item.dateTimeFormat, nameDisplayOrder: item.nameDisplayOrder, disableAnimations: false, largeEmoji: false) - - //let item2: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, context: item.context, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: item.strings.Appearance_PreviewIncomingText, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), disableDate: true) let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: item.strings.Appearance_PreviewIncomingText, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: TelegramUser(id: item.context.account.peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []), text: item.strings.Appearance_PreviewOutgoingText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) 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/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 42bf07db5b..293a4adf9f 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -2924,6 +2924,33 @@ public extension Api { }) } + public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-637606386) + serializeInt32(flags, buffer: buffer, boxed: false) + fromPeer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomId.count)) + for item in randomId { + serializeInt64(item, buffer: buffer, boxed: false) + } + toPeer.serialize(buffer, true) + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + public static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(570955184) @@ -2944,6 +2971,31 @@ public extension Api { }) } + public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1224152952) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + public static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-872345397) @@ -2966,33 +3018,6 @@ public extension Api { }) } - public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-637606386) - serializeInt32(flags, buffer: buffer, boxed: false) - fromPeer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomId.count)) - for item in randomId { - serializeInt64(item, buffer: buffer, boxed: false) - } - toPeer.serialize(buffer, true) - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - public static func getScheduledHistory(peer: Api.InputPeer, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-490575781) @@ -3064,31 +3089,6 @@ public extension Api { return result }) } - - public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1224152952) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -5683,15 +5683,16 @@ public extension Api { }) } - public static func updateTheme(flags: Int32, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-124161674) + buffer.appendInt32(999203330) serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(format, buffer: buffer, boxed: false) theme.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeString(slug!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} - return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", flags), ("theme", theme), ("slug", slug), ("title", title), ("document", document)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", flags), ("format", format), ("theme", theme), ("slug", slug), ("title", title), ("document", document)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in let reader = BufferReader(buffer) var result: Api.Theme? if let signature = reader.readInt32() { 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/HistoryViewStateValidation.swift b/submodules/TelegramCore/TelegramCore/HistoryViewStateValidation.swift index 6911b76297..12091534d8 100644 --- a/submodules/TelegramCore/TelegramCore/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/TelegramCore/HistoryViewStateValidation.swift @@ -138,6 +138,8 @@ final class HistoryViewStateValidationContexts { private var contexts: [Int32: HistoryStateValidationContext] = [:] + private var previousPeerValidationTimestamps: [PeerId: Double] = [:] + init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId) { self.queue = queue self.postbox = postbox @@ -264,21 +266,27 @@ final class HistoryViewStateValidationContexts { } else if view.namespaces.contains(Namespaces.Message.ScheduledCloud) { if let _ = self.contexts[id] { } else if let location = location, case let .peer(peerId) = location { - let context = HistoryStateValidationContext() - self.contexts[id] = context - - let disposable = MetaDisposable() - let batch = HistoryStateValidationBatch(disposable: disposable) - context.batch = batch - - let messages: [Message] = view.entries.map { $0.message }.filter { $0.id.namespace == Namespaces.Message.ScheduledCloud } + let timestamp = self.network.context.globalTime() + if let previousTimestamp = self.previousPeerValidationTimestamps[peerId], timestamp < previousTimestamp + 60 { + } else { + self.previousPeerValidationTimestamps[peerId] = timestamp + + let context = HistoryStateValidationContext() + self.contexts[id] = context - disposable.set((validateScheduledMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: peerId, tag: nil, messages: messages, historyState: .scheduledMessages(peerId)) - |> deliverOn(self.queue)).start(completed: { [weak self] in - if let strongSelf = self, let context = strongSelf.contexts[id] { - context.batch = nil - } - })) + let disposable = MetaDisposable() + let batch = HistoryStateValidationBatch(disposable: disposable) + context.batch = batch + + let messages: [Message] = view.entries.map { $0.message }.filter { $0.id.namespace == Namespaces.Message.ScheduledCloud } + + disposable.set((validateScheduledMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: peerId, tag: nil, messages: messages, historyState: .scheduledMessages(peerId)) + |> deliverOn(self.queue)).start(completed: { [weak self] in + if let strongSelf = self, let context = strongSelf.contexts[id] { + context.batch = nil + } + })) + } } } } diff --git a/submodules/TelegramCore/TelegramCore/MessageUtils.swift b/submodules/TelegramCore/TelegramCore/MessageUtils.swift index 1709b078fe..3a354e6fab 100644 --- a/submodules/TelegramCore/TelegramCore/MessageUtils.swift +++ b/submodules/TelegramCore/TelegramCore/MessageUtils.swift @@ -194,7 +194,7 @@ public extension Message { if self.flags.contains(.Failed) { return true } else if self.id.namespace == Namespaces.Message.ScheduledCloud { - return timestamp > self.timestamp + 30 + return timestamp > self.timestamp + 60 } else { return false } diff --git a/submodules/TelegramCore/TelegramCore/Themes.swift b/submodules/TelegramCore/TelegramCore/Themes.swift index e816cea045..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 { @@ -264,6 +264,7 @@ private func uploadTheme(account: Account, resource: MediaResource, thumbnailDat public enum CreateThemeError { case generic + case slugInvalid } public enum CreateThemeResult { @@ -279,7 +280,12 @@ public func createTheme(account: Account, title: String, resource: MediaResource case let .complete(file): if let resource = file.resource as? CloudDocumentMediaResource { return account.network.request(Api.functions.account.createTheme(slug: "", title: title, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)))) - |> mapError { _ in return CreateThemeError.generic } + |> mapError { error in + if error.errorDescription == "THEME_SLUG_INVALID" { + return .slugInvalid + } + return .generic + } |> mapToSignal { apiTheme -> Signal in if let theme = TelegramTheme(apiTheme: apiTheme) { return account.postbox.transaction { transaction -> CreateThemeResult in @@ -318,7 +324,7 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?, if let _ = title { flags |= 1 << 1 } - if let _ = slug { + if let slug = slug, !slug.isEmpty { flags |= 1 << 0 } if let _ = resource { @@ -352,8 +358,13 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?, inputDocument = nil } - return account.network.request(Api.functions.account.updateTheme(flags: flags, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument)) - |> mapError { _ in return CreateThemeError.generic } + return account.network.request(Api.functions.account.updateTheme(flags: flags, format: themeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), slug: slug, title: title, document: inputDocument)) + |> mapError { error in + if error.errorDescription == "THEME_SLUG_INVALID" { + return .slugInvalid + } + return .generic + } |> mapToSignal { apiTheme -> Signal in if let result = TelegramTheme(apiTheme: apiTheme) { return account.postbox.transaction { transaction -> CreateThemeResult in @@ -437,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 + } + } +} diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift index a18ed8f262..d4a617ca26 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift @@ -33,7 +33,7 @@ extension TelegramWallpaper: Codable { case "builtin": self = .builtin(WallpaperSettings()) default: - if let color = UIColor(hexString: value) { + if value.count == 6, let color = UIColor(hexString: value) { self = .color(Int32(bitPattern: color.rgb)) } else { self = .file(id: 0, accessHash: 0, isCreator: false, isDefault: false, isPattern: false, isDark: false, slug: value, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(blur: false, motion: false, color: nil, intensity: nil)) diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index b363bb461c..46d3d6a382 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -5561,7 +5561,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = (enqueueMessages(account: self.context.account, peerId: peerId, messages: self.transformEnqueueMessages(messages)) |> deliverOnMainQueue).start(next: { [weak self] _ in - self?.chatDisplayNode.historyNode.scrollToEndOfHistory() + if let strongSelf = self, !strongSelf.presentationInterfaceState.isScheduledMessages { + strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() + } }) self.donateIntent() diff --git a/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift b/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift index 7d22680efb..a1e8346487 100644 --- a/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift @@ -1075,27 +1075,43 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } public func scrollScreenToTop() { - var currentMessage: Message? - if let historyView = self.historyView { - if let visibleRange = self.displayedItemRange.loadedRange { - var index = historyView.filteredEntries.count - 1 - loop: for entry in historyView.filteredEntries { - if index >= visibleRange.firstIndex && index <= visibleRange.lastIndex { - if case let .MessageEntry(message, _, _, _, _, _) = entry { - currentMessage = message - break loop - } else if case let .MessageGroupEntry(_, messages, _) = entry { - currentMessage = messages.first?.0 - break loop - } + if let subject = self.subject, case .scheduledMessages = subject { + if let historyView = self.historyView { + if let entry = historyView.filteredEntries.first { + var currentMessage: Message? + if case let .MessageEntry(message, _, _, _, _, _) = entry { + currentMessage = message + } else if case let .MessageGroupEntry(_, messages, _) = entry { + currentMessage = messages.first?.0 + } + if let message = currentMessage, let anchorMessage = self.anchorMessageInCurrentHistoryView() { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(message.index), anchorIndex: .message(message.index), sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true), id: self.takeNextHistoryLocationId()) } - index -= 1 } } - } - - if let currentMessage = currentMessage { - self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(currentMessage.index), anchorIndex: .message(currentMessage.index), sourceIndex: .upperBound, scrollPosition: .top(0.0), animated: true), id: self.takeNextHistoryLocationId()) + } else { + var currentMessage: Message? + if let historyView = self.historyView { + if let visibleRange = self.displayedItemRange.loadedRange { + var index = historyView.filteredEntries.count - 1 + loop: for entry in historyView.filteredEntries { + if index >= visibleRange.firstIndex && index <= visibleRange.lastIndex { + if case let .MessageEntry(message, _, _, _, _, _) = entry { + currentMessage = message + break loop + } else if case let .MessageGroupEntry(_, messages, _) = entry { + currentMessage = messages.first?.0 + break loop + } + } + index -= 1 + } + } + } + + if let currentMessage = currentMessage { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(currentMessage.index), anchorIndex: .message(currentMessage.index), sourceIndex: .upperBound, scrollPosition: .top(0.0), animated: true), id: self.takeNextHistoryLocationId()) + } } } diff --git a/submodules/TelegramUI/TelegramUI/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/TelegramUI/ChatHistoryViewForLocation.swift index 8913fed713..c40297243b 100644 --- a/submodules/TelegramUI/TelegramUI/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/TelegramUI/ChatHistoryViewForLocation.swift @@ -53,7 +53,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A type = .Generic(type: .UpdateVisible) } } else { - type = .Generic(type: updateType) + type = .Generic(type: .Generic) } return .HistoryView(view: view, type: type, scrollPosition: scrollPosition, flashIndicators: false, originalScrollPosition: chatScrollPosition, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id) } diff --git a/submodules/TelegramUI/TelegramUI/DeclareEncodables.swift b/submodules/TelegramUI/TelegramUI/DeclareEncodables.swift index 11cc745962..5ba39fe5e9 100644 --- a/submodules/TelegramUI/TelegramUI/DeclareEncodables.swift +++ b/submodules/TelegramUI/TelegramUI/DeclareEncodables.swift @@ -8,6 +8,7 @@ import LocalMediaResources import WebSearchUI import InstantPageCache import SettingsUI +import WallpaperResources private var telegramUIDeclaredEncodables: Void = { declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) }) @@ -40,6 +41,7 @@ private var telegramUIDeclaredEncodables: Void = { declareEncodable(InstantPageStoredState.self, f: { InstantPageStoredState(decoder: $0) }) declareEncodable(InstantPageStoredDetailsState.self, f: { InstantPageStoredDetailsState(decoder: $0) }) declareEncodable(CachedInstantPage.self, f: { CachedInstantPage(decoder: $0) }) + declareEncodable(CachedWallpaper.self, f: { CachedWallpaper(decoder: $0) }) declareEncodable(WatchPresetSettings.self, f: { WatchPresetSettings(decoder: $0) }) declareEncodable(WebSearchSettings.self, f: { WebSearchSettings(decoder: $0) }) declareEncodable(RecentWebSearchQueryItem.self, f: { RecentWebSearchQueryItem(decoder: $0) }) diff --git a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift index 2eaeceb1b5..8856221b1d 100644 --- a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift +++ b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift @@ -367,6 +367,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { return nil })) case let .theme(media): + params.dismissInput() let path = params.context.account.postbox.mediaBox.completedResourcePath(media.resource) var previewTheme: PresentationTheme? if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) { @@ -462,9 +463,8 @@ func openChatTheme(context: AccountContext, message: Message, present: @escaping if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url) |> deliverOnMainQueue).start(next: { resolvedUrl in - if case let .theme(slug) = resolvedUrl { - let file = content.files!.first! - if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let theme = makePresentationTheme(data: data) { + if case let .theme(slug) = resolvedUrl, let files = content.files { + if let file = files.filter({ $0.mimeType == "application/x-tgtheme-ios" }).first, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let theme = makePresentationTheme(data: data) { let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file)) present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } diff --git a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift index 1832a1b0ad..21bbec0ad5 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift @@ -98,9 +98,17 @@ public enum PresentationThemeReference: PostboxCoding, Equatable { case 0: self = .builtin(PresentationBuiltinThemeReference(rawValue: decoder.decodeInt32ForKey("t", orElse: 0))!) case 1: - self = .local(decoder.decodeObjectForKey("localTheme", decoder: { PresentationLocalTheme(decoder: $0) }) as! PresentationLocalTheme) + if let localTheme = decoder.decodeObjectForKey("localTheme", decoder: { PresentationLocalTheme(decoder: $0) }) as? PresentationLocalTheme { + self = .local(localTheme) + } else { + self = .builtin(.dayClassic) + } case 2: - self = .cloud(decoder.decodeObjectForKey("cloudTheme", decoder: { PresentationCloudTheme(decoder: $0) }) as! PresentationCloudTheme) + if let cloudTheme = decoder.decodeObjectForKey("cloudTheme", decoder: { PresentationCloudTheme(decoder: $0) }) as? PresentationCloudTheme { + self = .cloud(cloudTheme) + } else { + self = .builtin(.dayClassic) + } default: assertionFailure() self = .builtin(.dayClassic) @@ -438,7 +446,7 @@ public struct PresentationThemeSettings: PreferencesEntry { public init(decoder: PostboxDecoder) { self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin(WallpaperSettings()) - self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as! PresentationThemeReference + self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as? PresentationThemeReference ?? .builtin(.dayClassic) self.themeSpecificChatWallpapers = decoder.decodeObjectDictionaryForKey("themeSpecificChatWallpapers", keyDecoder: { decoder in return decoder.decodeInt64ForKey("k", orElse: 0) diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 205b3fb165..f3b13736f7 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -11,7 +11,7 @@ import PhotoResources import LocalMediaResources import TelegramPresentationData -private func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { +private func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) { let maybeFullSize: Signal @@ -143,10 +143,15 @@ private func wallpaperDatas(account: Account, accountManager: AccountManager, fi } } } else { - - return thumbnailData |> mapToSignal { thumbnailData in + if onlyFullSize { return fullSizeData |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + return (nil, fullSizeData, complete) + } + } else { + return thumbnailData |> mapToSignal { thumbnailData in + return fullSizeData |> map { (fullSizeData, complete) in + return (thumbnailData, fullSizeData, complete) + } } } } @@ -158,11 +163,14 @@ private func wallpaperDatas(account: Account, accountManager: AccountManager, fi } } -public func wallpaperImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = wallpaperDatas(account: account, accountManager: accountManager, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, thumbnail: thumbnail, autoFetchFullSize: autoFetchFullSize, synchronousLoad: synchronousLoad) +public func wallpaperImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let signal = wallpaperDatas(account: account, accountManager: accountManager, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, thumbnail: thumbnail, onlyFullSize: onlyFullSize, autoFetchFullSize: autoFetchFullSize, synchronousLoad: synchronousLoad) return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + if let fullSizeData = fullSizeData, let fileReference = fileReference { + accountManager.mediaBox.storeResourceData(fileReference.media.resource.id, data: fullSizeData) + } return { arguments in let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize @@ -633,7 +641,7 @@ private func generateBackArrowImage(color: UIColor) -> UIImage? { }) } -public func drawThemeImage(context c: CGContext, theme: PresentationTheme, size: CGSize) { +public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallpaperImage: UIImage? = nil, size: CGSize) { let drawingRect = CGRect(origin: CGPoint(), size: size) switch theme.chat.defaultWallpaper { @@ -648,6 +656,11 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, size: case let .file(file): c.setFillColor(theme.chatList.backgroundColor.cgColor) c.fill(drawingRect) + + if let image = wallpaperImage, let cgImage = image.cgImage { + let size = image.size.aspectFilled(drawingRect.size) + c.draw(cgImage, in: CGRect(origin: CGPoint(x: (drawingRect.size.width - size.width) / 2.0, y: (drawingRect.size.height - size.height) / 2.0), size: size)) + } default: break } @@ -714,30 +727,162 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, size: } public func themeImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - return telegramThemeData(account: account, accountManager: accountManager, resource: fileReference.media.resource, synchronousLoad: synchronousLoad) - |> map { data in - let theme: PresentationTheme? - if let data = data { - theme = makePresentationTheme(data: data) + let maybeFetched = accountManager.mediaBox.resourceData(fileReference.media.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) + let data = maybeFetched + |> take(1) + |> mapToSignal { maybeData -> Signal<(Data?, Data?), NoError> in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single((loadedData, nil)) } else { - theme = nil + let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) + + let previewRepresentation = fileReference.media.previewRepresentations.first + let fetchedThumbnail: Signal + if let previewRepresentation = previewRepresentation { + fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(previewRepresentation.resource)) + } else { + fetchedThumbnail = .complete() + } + + let thumbnailData: Signal + if let previewRepresentation = previewRepresentation { + thumbnailData = Signal { subscriber in + let fetchedDisposable = fetchedThumbnail.start() + let thumbnailDisposable = account.postbox.mediaBox.resourceData(previewRepresentation.resource).start(next: { next in + let data = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) + if let data = data, data.count > 0 { + subscriber.putNext(data) + } else { + subscriber.putNext(decodedThumbnailData) + } + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } + } + } else { + thumbnailData = .single(decodedThumbnailData) + } + + let reference = fileReference.resourceReference(fileReference.media.resource) + let fullSizeData = Signal { subscriber in + let fetch = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: reference).start() + let disposable = (account.postbox.mediaBox.resourceData(reference.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false) + |> map { data -> Data? in + return data.complete ? try? Data(contentsOf: URL(fileURLWithPath: data.path)) : nil + }).start(next: { next in + if let data = next { + accountManager.mediaBox.storeResourceData(reference.resource.id, data: data) + } + subscriber.putNext(next) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + return ActionDisposable { + fetch.dispose() + disposable.dispose() + } + } + + return thumbnailData |> mapToSignal { thumbnailData in + return fullSizeData |> map { fullSizeData in + return (fullSizeData, thumbnailData) + } + } } + } + |> mapToSignal { (fullSizeData, thumbnailData) -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in + if let fullSizeData = fullSizeData, let theme = makePresentationTheme(data: fullSizeData) { + if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { + return cachedWallpaper(account: account, slug: file.slug) + |> mapToSignal { wallpaper -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in + if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper { + var convertedRepresentations: [ImageRepresentationWithReference] = [] + convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .wallpaper(resource: file.file.resource))) + return wallpaperImage(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) + |> map { _ -> (PresentationTheme?, UIImage?, Data?) in + if let path = accountManager.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let image = UIImage(data: data) { + return (theme, image, thumbnailData) + } else { + return (theme, nil, thumbnailData) + } + } + } else { + return .single((theme, nil, thumbnailData)) + } + } + } else { + return .single((theme, nil, thumbnailData)) + } + } else { + return .single((nil, nil, thumbnailData)) + } + } + return data + |> map { theme, wallpaperImage, thumbnailData in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: 0.0, clear: true) + var thumbnailImage: CGImage? + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + thumbnailImage = image + } + + var blurredThumbnailImage: UIImage? + if let thumbnailImage = thumbnailImage { + let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) + if thumbnailSize.width > 200.0 { + blurredThumbnailImage = UIImage(cgImage: thumbnailImage) + } else { + let initialThumbnailContextFittingSize = arguments.imageSize.fitted(CGSize(width: 90.0, height: 90.0)) + + let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + blurredThumbnailImage = thumbnailContext.generateImage() + } + } + let drawingRect = arguments.drawingRect - context.withFlippedContext { c in - c.setBlendMode(.normal) - if let theme = theme { - drawThemeImage(context: c, theme: theme, size: arguments.drawingSize) + if let blurredThumbnailImage = blurredThumbnailImage, theme == nil { + let context = DrawingContext(size: arguments.drawingSize, scale: 0.0, clear: true) + context.withFlippedContext { c in + c.setBlendMode(.copy) + if let cgImage = blurredThumbnailImage.cgImage { + c.interpolationQuality = .none + let fittedSize = blurredThumbnailImage.size.aspectFilled(arguments.drawingSize) + drawImage(context: c, image: cgImage, orientation: .up, in: CGRect(origin: CGPoint(x: (drawingRect.width - fittedSize.width) / 2.0, y: (drawingRect.height - fittedSize.height) / 2.0), size: fittedSize)) + c.setBlendMode(.normal) + } + } + addCorners(context, arguments: arguments) + return context + } + + let context = DrawingContext(size: arguments.drawingSize, scale: 0.0, clear: true) + if let theme = theme { + context.withFlippedContext { c in + c.setBlendMode(.normal) + + drawThemeImage(context: c, theme: theme, wallpaperImage: wallpaperImage, size: arguments.drawingSize) c.setStrokeColor(theme.rootController.navigationBar.separatorColor.cgColor) c.setLineWidth(2.0) let borderPath = UIBezierPath(roundedRect: drawingRect, cornerRadius: 4.0) c.addPath(borderPath.cgPath) c.drawPath(using: .stroke) - } else if let emptyColor = arguments.emptyColor { - c.setFillColor(emptyColor.cgColor) - c.fill(drawingRect) } } addCorners(context, arguments: arguments)