diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index f487613827..92e97264fc 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -226,20 +226,24 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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(theme): - 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) { + 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 - return theme.withUpdated(name: nil, author: nil, defaultWallpaper: wallpaper?.wallpaper) + 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)) + previewThemePromise.set(.single(theme.withUpdated(name: nil, author: nil, defaultWallpaper: info.resolvedWallpaper))) } } else { previewThemePromise.set(.single(context.sharedContext.currentPresentationData.with { $0 }.theme)) } - initialState = EditThemeControllerState(mode: mode, title: theme.theme.title, slug: theme.theme.slug, 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) @@ -373,9 +377,13 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll 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, size: size) + drawThemeImage(context: c, theme: theme, wallpaperImage: wallpaperImage, size: size) })?.cgImage { context.draw(image, in: CGRect(origin: CGPoint(), size: size)) } @@ -410,17 +418,14 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll } 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: { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index fee231be66..5f0bcd8983 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..d79d7080de 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) @@ -22,6 +23,7 @@ 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 @@ -57,6 +59,15 @@ 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) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 419713d492..535ecd470f 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -30,6 +30,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,13 +42,15 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { private var chatNodes: [ListViewItemNode]? private let maskNode: ASImageNode - private let chatBackgroundNode: WallpaperBackgroundNode + 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? init(context: AccountContext, previewTheme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping () -> Void) { @@ -63,17 +67,20 @@ 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.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.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings) @@ -94,7 +101,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 +114,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.addSubnode(self.toolbarNode) self.scrollNode.addSubnode(self.chatListBackgroundNode) - self.scrollNode.addSubnode(self.chatBackgroundNode) + self.scrollNode.addSubnode(self.instantChatBackgroundNode) + self.scrollNode.addSubnode(self.remoteChatBackgroundNode) self.toolbarNode.cancel = { dismiss() @@ -116,7 +124,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 { @@ -294,7 +305,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.instantChatBackgroundNode.addSubnode(itemNode!) } self.messageNodes = messageNodes } @@ -315,7 +326,8 @@ 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.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.scrollNode.view.contentSize = CGSize(width: bounds.width * 2.0, height: bounds.height) 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/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 b8ee8e6d9c..c081e71947 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/OpenChatMessage.swift b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift index 447b148095..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) { 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 f46d37391e..f3b13736f7 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -641,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 { @@ -656,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 } @@ -722,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)