From f910aa25699710950ec6e7959adfd8c958003b47 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 30 Aug 2019 04:45:08 +0400 Subject: [PATCH 1/8] Fix RTLO --- submodules/Display/Display/TextNode.swift | 6 +++--- submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift | 4 ++-- submodules/TelegramUI/TelegramUI/ChatController.swift | 8 +++++--- submodules/TelegramUI/TelegramUI/ChatControllerNode.swift | 8 +++++++- .../TelegramUI/TelegramUI/ChatMessageActionItemNode.swift | 4 ++-- .../TelegramUI/ChatMessageAttachedContentNode.swift | 4 ++-- .../TelegramUI/ChatMessagePollBubbleContentNode.swift | 4 ++-- .../TelegramUI/ChatMessageTextBubbleContentNode.swift | 8 ++++---- submodules/UrlEscaping/Sources/UrlEscaping.swift | 8 +++----- 9 files changed, 30 insertions(+), 24 deletions(-) diff --git a/submodules/Display/Display/TextNode.swift b/submodules/Display/Display/TextNode.swift index 77b59c9d30..7217a3cb4f 100644 --- a/submodules/Display/Display/TextNode.swift +++ b/submodules/Display/Display/TextNode.swift @@ -495,12 +495,12 @@ public final class TextNodeLayout: NSObject { return result } - public func attributeSubstring(name: String, index: Int) -> String? { + public func attributeSubstring(name: String, index: Int) -> (String, String)? { if let attributedString = self.attributedString { var range = NSRange() let _ = attributedString.attribute(NSAttributedString.Key(rawValue: name), at: index, effectiveRange: &range) if range.length != 0 { - return (attributedString.string as NSString).substring(with: range) + return ((attributedString.string as NSString).substring(with: range), attributedString.string) } } return nil @@ -755,7 +755,7 @@ public class TextNode: ASDisplayNode { return self.cachedLayout?.textRangesRects(text: text) ?? [] } - public func attributeSubstring(name: String, index: Int) -> String? { + public func attributeSubstring(name: String, index: Int) -> (String, String)? { return self.cachedLayout?.attributeSubstring(name: name, index: index) } diff --git a/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift b/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift index 8097d213a2..4e57b48327 100644 --- a/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift @@ -268,8 +268,8 @@ final class ChatBotInfoItemNode: ListViewItemNode { if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { var concealed = true - if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { - concealed = !doesUrlMatchText(url: url, text: attributeText) + if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { + concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText) } return .url(url: url, concealed: concealed) } else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 46d3d6a382..666fd04d13 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -6875,11 +6875,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if concealed, let parsedUrlValue = parsedUrlValue, (parsedUrlValue.scheme == "http" || parsedUrlValue.scheme == "https"), !isConcealedUrlWhitelisted(parsedUrlValue) { - var displayUrl = url + var rawDisplayUrl = url let maxLength = 180 - if displayUrl.count > maxLength { - displayUrl = String(displayUrl[.. maxLength { + rawDisplayUrl = String(rawDisplayUrl[.. Bool { - for c in url { - if !c.isASCII { - return false - } +public func doesUrlMatchText(url: String, text: String, fullText: String) -> Bool { + if fullText.range(of: "\u{202e}") != nil { + return false } if url == text { return true From 7f37307c3390e84702b8e94ae5221910593c9551 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Aug 2019 03:46:02 +0300 Subject: [PATCH 2/8] Cloud themes improvements --- Telegram-iOS/en.lproj/Localizable.strings | 2 +- .../Sources/RadialStatusBackgroundNode.swift | 3 +- .../Sources/RadialStatusNode.swift | 18 +- .../Sources/Themes/EditThemeController.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 18 +- .../Themes/ThemePreviewController.swift | 221 +++++++++++++----- .../Themes/ThemePreviewControllerNode.swift | 33 ++- .../Themes/ThemeSettingsController.swift | 2 +- .../DefaultDarkTintedPresentationTheme.swift | 14 +- .../Sources/MakePresentationTheme.swift | 2 +- .../Sources/PresentationData.swift | 2 - .../Sources/PresentationThemeCodable.swift | 2 +- .../PresentationThemeEssentialGraphics.swift | 4 +- .../ChatMessageInteractiveFileNode.swift | 1 + .../Sources/PresentationThemeSettings.swift | 15 +- .../Sources/WallpaperResources.swift | 4 +- 16 files changed, 239 insertions(+), 104 deletions(-) diff --git a/Telegram-iOS/en.lproj/Localizable.strings b/Telegram-iOS/en.lproj/Localizable.strings index 503bc52b84..4b8cd46c00 100644 --- a/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram-iOS/en.lproj/Localizable.strings @@ -4636,7 +4636,7 @@ Any member of this group will be able to see messages in the channel."; "AccentColor.Title" = "Accent Color"; "Appearance.ThemePreview.ChatList.1.Name" = "Alicia Torreaux"; -"Appearance.ThemePreview.ChatList.1.Text" = "Bob says hi."; +"Appearance.ThemePreview.ChatList.1.Text" = "Bob says hi. 😊 ❤️ 😱"; "Appearance.ThemePreview.ChatList.2.Name" = "Roberto"; "Appearance.ThemePreview.ChatList.2.Text" = "Say hello to Alice 👋"; "Appearance.ThemePreview.ChatList.3.Name" = "Campus Public Chat"; diff --git a/submodules/RadialStatusNode/Sources/RadialStatusBackgroundNode.swift b/submodules/RadialStatusNode/Sources/RadialStatusBackgroundNode.swift index 1570bdfc63..d32ae107a7 100644 --- a/submodules/RadialStatusNode/Sources/RadialStatusBackgroundNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialStatusBackgroundNode.swift @@ -17,11 +17,12 @@ final class RadialStatusBackgroundNode: ASDisplayNode { } } - init(color: UIColor) { + init(color: UIColor, synchronous: Bool) { self.color = color super.init() + self.displaysAsynchronously = !synchronous self.isLayerBacked = true self.isOpaque = false } diff --git a/submodules/RadialStatusNode/Sources/RadialStatusNode.swift b/submodules/RadialStatusNode/Sources/RadialStatusNode.swift index ef186261da..6ac5eef755 100644 --- a/submodules/RadialStatusNode/Sources/RadialStatusNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialStatusNode.swift @@ -187,7 +187,7 @@ public enum RadialStatusNodeState: Equatable { public final class RadialStatusNode: ASControlNode { public var backgroundNodeColor: UIColor { didSet { - self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, completion: {}) + self.transitionToBackgroundColor(self.state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: false, synchronous: false, completion: {}) } } @@ -215,16 +215,16 @@ public final class RadialStatusNode: ASControlNode { let contentNode = state.contentNode(current: self.contentNode, synchronous: synchronous) if contentNode !== self.contentNode { - self.transitionToContentNode(contentNode, state: state, fromState: fromState, backgroundColor: state.backgroundColor(color: self.backgroundNodeColor), animated: animated, completion: completion) + self.transitionToContentNode(contentNode, state: state, fromState: fromState, backgroundColor: state.backgroundColor(color: self.backgroundNodeColor), animated: animated, synchronous: synchronous, completion: completion) } else { - self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: animated, completion: completion) + self.transitionToBackgroundColor(state.backgroundColor(color: self.backgroundNodeColor), previousContentNode: nil, animated: animated, synchronous: synchronous, completion: completion) } } else { completion() } } - private func transitionToContentNode(_ node: RadialStatusContentNode?, state: RadialStatusNodeState, fromState: RadialStatusNodeState, backgroundColor: UIColor?, animated: Bool, completion: @escaping () -> Void) { + private func transitionToContentNode(_ node: RadialStatusContentNode?, state: RadialStatusNodeState, fromState: RadialStatusNodeState, backgroundColor: UIColor?, animated: Bool, synchronous: Bool = false, completion: @escaping () -> Void) { if let contentNode = self.contentNode { self.nextContentNode = node contentNode.enqueueReadyForTransition { [weak contentNode, weak self] in @@ -242,7 +242,7 @@ public final class RadialStatusNode: ASControlNode { contentNode.animateIn(from: fromState, delay: delay) } } - strongSelf.transitionToBackgroundColor(strongSelf.contentNode != nil ? backgroundColor : nil, previousContentNode: previousContentNode, animated: animated, completion: completion) + strongSelf.transitionToBackgroundColor(strongSelf.contentNode != nil ? backgroundColor : nil, previousContentNode: previousContentNode, animated: animated, synchronous: synchronous, completion: completion) }) previousContentNode.animateOut(to: state, completion: { [weak contentNode] in if let strongSelf = self, let contentNode = contentNode { @@ -262,7 +262,7 @@ public final class RadialStatusNode: ASControlNode { contentNode.layout() } } - strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion) + strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, synchronous: synchronous, completion: completion) } } } @@ -282,11 +282,11 @@ public final class RadialStatusNode: ASControlNode { } } } - self.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, completion: completion) + self.transitionToBackgroundColor(backgroundColor, previousContentNode: nil, animated: animated, synchronous: synchronous, completion: completion) } } - private func transitionToBackgroundColor(_ color: UIColor?, previousContentNode: RadialStatusContentNode?, animated: Bool, completion: @escaping () -> Void) { + private func transitionToBackgroundColor(_ color: UIColor?, previousContentNode: RadialStatusContentNode?, animated: Bool, synchronous: Bool, completion: @escaping () -> Void) { let currentColor = self.backgroundNode?.color var updated = false @@ -302,7 +302,7 @@ public final class RadialStatusNode: ASControlNode { backgroundNode.color = color completion() } else { - let backgroundNode = RadialStatusBackgroundNode(color: color) + let backgroundNode = RadialStatusBackgroundNode(color: color, synchronous: synchronous) backgroundNode.frame = self.bounds self.backgroundNode = backgroundNode self.insertSubnode(backgroundNode, at: 0) diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index 92e97264fc..abdb484974 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -267,7 +267,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll |> 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))) + convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), 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 diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index ccba89f347..c8d996dbd4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -11,7 +11,7 @@ import ChatListUI import AccountContext private func generateMaskImage(color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 1.0, height: 60.0), opaque: false, rotatedContext: { size, context in + return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -21,7 +21,7 @@ private func generateMaskImage(color: UIColor) -> UIImage? { let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 60.0), options: CGGradientDrawingOptions()) + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 80.0), options: CGGradientDrawingOptions()) }) } @@ -82,8 +82,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate self.chatListBackgroundNode = ASDisplayNode() self.chatBackgroundNode = WallpaperBackgroundNode() self.chatBackgroundNode.displaysAsynchronously = false - self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: theme, wallpaper: self.presentationData.chatWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) - self.chatBackgroundNode.motionEnabled = self.presentationData.chatWallpaper.settings?.motion ?? false + if case .color = self.presentationData.chatWallpaper { + } else { + self.chatBackgroundNode.image = chatControllerBackgroundImage(theme: theme, wallpaper: self.presentationData.chatWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) + self.chatBackgroundNode.motionEnabled = self.presentationData.chatWallpaper.settings?.motion ?? false + } self.colorPanelNode = WallpaperColorPanelNode(theme: self.theme, strings: self.presentationData.strings) self.colorPanelNode.color = color @@ -233,8 +236,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let timestamp1 = timestamp + 120 items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) @@ -305,7 +309,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.theme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local))) let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) @@ -388,6 +392,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate self.pageControlNode.frame = pageControlFrame self.pageControlBackgroundNode.frame = CGRect(x: pageControlFrame.minX - 11.0, y: pageControlFrame.minY - 12.0, width: pageControlFrame.width + 22.0, height: 30.0) - transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - bottomInset - 60.0, width: bounds.width, height: 60.0)) + transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - bottomInset - 80.0, width: bounds.width, height: 80.0)) } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index c2bcfa24e4..2e0519c277 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -11,6 +11,7 @@ import AccountContext import ShareController import CounterContollerTitleView import WallpaperResources +import OverlayStatusController public enum ThemePreviewSource { case theme(TelegramTheme) @@ -32,6 +33,8 @@ public final class ThemePreviewController: ViewController { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? + + private var applyDisposable = MetaDisposable() public init(context: AccountContext, previewTheme: PresentationTheme, source: ThemePreviewSource) { self.context = context @@ -75,7 +78,6 @@ public final class ThemePreviewController: ViewController { |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { strongSelf.presentationData = presentationData - strongSelf.updateStrings() } }) } @@ -86,6 +88,7 @@ public final class ThemePreviewController: ViewController { deinit { self.presentationDataDisposable?.dispose() + self.applyDisposable.dispose() } override public func viewDidAppear(_ animated: Bool) { @@ -108,63 +111,7 @@ public final class ThemePreviewController: ViewController { } }, apply: { [weak self] in if let strongSelf = self { - let previewTheme = strongSelf.previewTheme - let theme: Signal - - switch strongSelf.source { - case .theme, .slug: - theme = combineLatest(strongSelf.theme.get() |> take(1), strongSelf.controllerNode.wallpaperPromise.get() |> take(1)) - |> map { theme, wallpaper in - if let theme = theme { - 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 - } - } - case .media: - if let strings = encodePresentationTheme(previewTheme), let data = strings.data(using: .utf8) { - let resource = LocalFileMediaResource(fileId: arc4random64()) - strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) - - theme = .single(.local(PresentationLocalTheme(title: previewTheme.name.string, resource: resource))) - } else { - theme = .single(.builtin(.dayClassic)) - } - } - - let signal = theme - |> mapToSignal { theme -> Signal in - guard let theme = theme else { - return .complete() - } - return strongSelf.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 - } - return PresentationThemeSettings(chatWallpaper: previewTheme.chat.defaultWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) - }) - } - } - -// 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) -// } - - let _ = (signal |> deliverOnMainQueue).start(completed: { [weak self] in - if let strongSelf = self { - strongSelf.dismiss() - } - }) + strongSelf.apply() } }) self.displayNodeDidLoad() @@ -180,8 +127,162 @@ public final class ThemePreviewController: ViewController { } } - private func updateStrings() { - + private func apply() { + let previewTheme = self.previewTheme + let theme: Signal + let context = self.context + let wallpaperPromise = self.controllerNode.wallpaperPromise + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let disposable = self.applyDisposable + + switch self.source { + case .theme, .slug: + theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1)) + |> map { theme, wallpaper in + if let theme = theme { + 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 + } + } + case .media: + if let strings = encodePresentationTheme(previewTheme), let data = strings.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) + theme = .single(.local(PresentationLocalTheme(title: previewTheme.name.string, resource: resource, resolvedWallpaper: nil))) + } else { + theme = .single(.builtin(.dayClassic)) + } + } + + var resolvedWallpaper: TelegramWallpaper? + + let signal = theme + |> mapToSignal { theme -> Signal in + guard let theme = theme else { + return .complete() + } + switch theme { + case .cloud: + return .single(theme) + case let .local(info): + return wallpaperPromise.get() + |> take(1) + |> mapToSignal { currentWallpaper -> Signal in + if case let .file(file) = currentWallpaper, file.id != 0 { + resolvedWallpaper = currentWallpaper + } + + var wallpaperImage: UIImage? + if case .file = currentWallpaper { + wallpaperImage = chatControllerBackgroundImage(theme: previewTheme, wallpaper: currentWallpaper, 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: previewTheme, wallpaperImage: wallpaperImage, size: size) + })?.cgImage { + context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + } + }, scale: 1.0) + let themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6) + + return telegramThemes(postbox: context.account.postbox, network: context.account.network) + |> take(1) + |> mapToSignal { themes -> Signal in + let similarTheme = themes.filter { $0.isCreator && $0.title == info.title }.first + if let similarTheme = similarTheme { + return updateTheme(account: context.account, theme: similarTheme, title: nil, slug: nil, resource: info.resource, thumbnailData: themeThumbnailData) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + let updatedTheme = PresentationLocalTheme(title: info.title, resource: info.resource, resolvedWallpaper: resolvedWallpaper) + return .single(.local(updatedTheme)) + } + if case let .result(theme) = result, let file = theme.file { + context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id) + return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper))) + } else { + return .complete() + } + } + + } else { + return createTheme(account: context.account, title: info.title, resource: info.resource, thumbnailData: themeThumbnailData) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + let updatedTheme = PresentationLocalTheme(title: info.title, resource: info.resource, resolvedWallpaper: resolvedWallpaper) + return .single(.local(updatedTheme)) + } + if case let .result(updatedTheme) = result, let file = updatedTheme.file { + context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id) + return .single(.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper))) + } else { + return .complete() + } + } + } + } + } + case .builtin: + return .single(theme) + } + } + |> mapToSignal { theme -> Signal in + if case let .cloud(info) = theme { + let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: info.theme).start() + let _ = saveThemeInteractively(account: context.account, theme: info.theme).start() + } + return context.sharedContext.accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in + let current = entry as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings + return PresentationThemeSettings(chatWallpaper: resolvedWallpaper ?? previewTheme.chat.defaultWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + }) + } + } + + var cancelImpl: (() -> Void)? + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: { + cancelImpl?() + })) + self?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.7, queue: Queue.mainQueue()) + + let progressDisposable = progressSignal.start() + cancelImpl = { + disposable.set(nil) + } + disposable.set((signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + |> deliverOnMainQueue).start(completed: {[weak self] in + if let strongSelf = self { + strongSelf.dismiss() + } + })) } override public func dismiss(completion: (() -> Void)? = nil) { diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index f7cd71c286..a74f41952d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -12,7 +12,7 @@ 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 + return generateImage(CGSize(width: 1.0, height: 80.0), opaque: false, rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -22,7 +22,7 @@ private func generateMaskImage(color: UIColor) -> UIImage? { let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 60.0), options: CGGradientDrawingOptions()) + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 80.0), options: CGGradientDrawingOptions()) }) } @@ -55,6 +55,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { private var wallpaperDisposable: Disposable? private var colorDisposable: Disposable? private var statusDisposable: Disposable? + private var fetchDisposable = MetaDisposable() init(context: AccountContext, previewTheme: PresentationTheme, dismiss: @escaping () -> Void, apply: @escaping () -> Void) { self.context = context @@ -140,8 +141,12 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.colorDisposable = (self.wallpaperPromise.get() - |> mapToSignal { wallpaper in - return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.account.postbox.mediaBox) + |> mapToSignal { wallpaper -> Signal in + if case let .file(file) = wallpaper, file.id == 0 { + return .complete() + } else { + return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.account.postbox.mediaBox) + } } |> deliverOnMainQueue).start(next: { [weak self] color in if let strongSelf = self { @@ -161,16 +166,18 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { 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: representation, reference: MediaResourceReference.media(media: .standalone(media: file.file), resource: representation.resource))) } - convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .wallpaper(resource: file.file.resource))) + convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .media(media: .standalone(media: file.file), 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) + let signal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: fileReference, representations: convertedRepresentations, alwaysShowThumbnailFirst: false, autoFetchFullSize: false) strongSelf.remoteChatBackgroundNode.setSignal(signal) + + strongSelf.fetchDisposable.set(freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file.file)).start()) let account = strongSelf.context.account let statusSignal = strongSelf.context.sharedContext.accountManager.mediaBox.resourceStatus(file.file.resource) @@ -199,6 +206,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.colorDisposable?.dispose() self.wallpaperDisposable?.dispose() self.statusDisposable?.dispose() + self.fetchDisposable.dispose() } override func didLoad() { @@ -250,8 +258,9 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let timestamp1 = timestamp + 120 items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, notificationSettings: nil, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) @@ -262,7 +271,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let timestamp5 = timestamp + 1000 items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer7), combinedReadState: nil, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) @@ -329,7 +338,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, message: message3, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.previewTheme.chat.defaultWallpaper, fontSize: self.presentationData.fontSize, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local))) let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) @@ -400,6 +409,6 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let pageControlFrame = CGRect(origin: CGPoint(x: floor((bounds.width - pageControlSize.width) / 2.0), y: layout.size.height - toolbarHeight - 42.0), size: pageControlSize) self.pageControlNode.frame = pageControlFrame self.pageControlBackgroundNode.frame = CGRect(x: pageControlFrame.minX - 11.0, y: pageControlFrame.minY - 12.0, width: pageControlFrame.width + 22.0, height: 30.0) - transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - toolbarHeight - 60.0, width: bounds.width, height: 60.0)) + transition.updateFrame(node: self.maskNode, frame: CGRect(x: 0.0, y: layout.size.height - toolbarHeight - 80.0, width: bounds.width, height: 80.0)) } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 8a51e2d4aa..4b147ac8d1 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -299,7 +299,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { arguments.selectTheme(theme) } }, longTapped: { theme in - arguments.presentThemeMenu(theme, theme == currentTheme) + arguments.presentThemeMenu(theme, theme.index == currentTheme.index) }) case let .iconHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index 081d0c067f..1b889822ac 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -11,6 +11,14 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta let hsv = accentColor.hsv accentColor = UIColor(hue: hsv.0, saturation: hsv.1, brightness: max(hsv.2, 0.18), alpha: 1.0) + let secondaryBadgeTextColor: UIColor + let lightness = accentColor.lightness + if lightness > 0.7 { + secondaryBadgeTextColor = .black + } else { + secondaryBadgeTextColor = .white + } + let mainBackgroundColor = accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25) let mainSelectionColor = accentColor.withMultiplied(hue: 1.03, saturation: 0.585, brightness: 0.12) let additionalBackgroundColor = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18) @@ -126,7 +134,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta itemCheckColors: PresentationThemeFillStrokeForeground( fillColor: accentColor, strokeColor: mainSecondaryTextColor.withAlphaComponent(0.5), - foregroundColor: .white + foregroundColor: secondaryBadgeTextColor ), controlSecondaryColor: mainSecondaryTextColor.withAlphaComponent(0.5), freeInputField: PresentationInputFieldTheme( @@ -161,7 +169,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta failedForegroundColor: .white, muteIconColor: mainSecondaryTextColor.withAlphaComponent(0.4), unreadBadgeActiveBackgroundColor: accentColor, - unreadBadgeActiveTextColor: UIColor(rgb: 0xffffff), + unreadBadgeActiveTextColor: secondaryBadgeTextColor, unreadBadgeInactiveBackgroundColor: mainSecondaryTextColor.withAlphaComponent(0.4), unreadBadgeInactiveTextColor: additionalBackgroundColor, pinnedBadgeColor: mainSecondaryTextColor.withAlphaComponent(0.5), @@ -293,7 +301,7 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta inputPlaceholderColor: mainSecondaryColor, inputTextColor: .white, inputClearButtonColor: mainSecondaryColor, - checkContentColor: .white + checkContentColor: secondaryBadgeTextColor ) let contextMenu = PresentationThemeContextMenu( diff --git a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift index 8ebaa39e47..fccf10791e 100644 --- a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift @@ -24,7 +24,7 @@ public func makePresentationTheme(mediaBox: MediaBox, themeReference: Presentati case let .builtin(reference): theme = makeDefaultPresentationTheme(reference: reference, accentColor: accentColor, serviceBackgroundColor: serviceBackgroundColor, baseColor: baseColor, preview: preview) case let .local(info): - if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data) { + if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) { theme = loadedTheme } else { theme = makeDefaultPresentationTheme(reference: .dayClassic, accentColor: nil, serviceBackgroundColor: serviceBackgroundColor, baseColor: baseColor, preview: preview) diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index e9bdad40b0..dd3cc87ac1 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -465,14 +465,12 @@ public func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, mediaBox: M } } else { return Signal { subscriber in - let fetch = mediaBox.fetchedResource(file.file.resource, parameters: nil).start() let data = serviceColor(for: mediaBox.resourceData(file.file.resource)).start(next: { next in subscriber.putNext(next) }, completed: { subscriber.putCompletion() }) return ActionDisposable { - fetch.dispose() data.dispose() } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift index d4a617ca26..b042ce1bc1 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 value.count == 6, let color = UIColor(hexString: value) { + if [6,7].contains(value.count), 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/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 3a995bb505..93153d7aea 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -225,13 +225,13 @@ public final class PrincipalThemeEssentialGraphics { self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor)! self.chatMessageBackgroundIncomingHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedTopMaskImage = emptyImage - self.chatMessageBackgroundIncomingMergedTopImage = emptyImage + self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout) self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedTopSideMaskImage = emptyImage self.chatMessageBackgroundIncomingMergedTopSideImage = emptyImage self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedBottomMaskImage = emptyImage - self.chatMessageBackgroundIncomingMergedBottomImage = emptyImage + self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout) self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedBothMaskImage = emptyImage self.chatMessageBackgroundIncomingMergedBothImage = emptyImage diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveFileNode.swift index 835a57b5e0..87d4caf3a1 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -627,6 +627,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { })) } + strongSelf.waveformNode.displaysAsynchronously = !presentationData.isPreview strongSelf.statusNode?.frame = progressFrame strongSelf.progressFrame = progressFrame strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame diff --git a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift index 21bbec0ad5..e8eae936c2 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift @@ -29,20 +29,28 @@ public struct WallpaperPresentationOptions: OptionSet { public struct PresentationLocalTheme: PostboxCoding, Equatable { public let title: String public let resource: LocalFileMediaResource + public let resolvedWallpaper: TelegramWallpaper? - public init(title: String, resource: LocalFileMediaResource) { + public init(title: String, resource: LocalFileMediaResource, resolvedWallpaper: TelegramWallpaper?) { self.title = title self.resource = resource + self.resolvedWallpaper = resolvedWallpaper } public init(decoder: PostboxDecoder) { self.title = decoder.decodeStringForKey("title", orElse: "") self.resource = decoder.decodeObjectForKey("resource", decoder: { LocalFileMediaResource(decoder: $0) }) as! LocalFileMediaResource + self.resolvedWallpaper = decoder.decodeObjectForKey("wallpaper", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper } public func encode(_ encoder: PostboxEncoder) { encoder.encodeString(self.title, forKey: "title") encoder.encodeObject(self.resource, forKey: "resource") + if let resolvedWallpaper = self.resolvedWallpaper { + encoder.encodeObject(resolvedWallpaper, forKey: "wallpaper") + } else { + encoder.encodeNil(forKey: "wallpaper") + } } public static func ==(lhs: PresentationLocalTheme, rhs: PresentationLocalTheme) -> Bool { @@ -52,6 +60,9 @@ public struct PresentationLocalTheme: PostboxCoding, Equatable { if !lhs.resource.isEqual(to: rhs.resource) { return false } + if lhs.resolvedWallpaper != rhs.resolvedWallpaper { + return false + } return true } } @@ -74,6 +85,8 @@ public struct PresentationCloudTheme: PostboxCoding, Equatable { encoder.encodeObject(self.theme, forKey: "theme") if let resolvedWallpaper = self.resolvedWallpaper { encoder.encodeObject(resolvedWallpaper, forKey: "wallpaper") + } else { + encoder.encodeNil(forKey: "wallpaper") } } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index f3b13736f7..a2488a3c28 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -168,7 +168,7 @@ public func wallpaperImage(account: Account, accountManager: AccountManager, fil return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in - if let fullSizeData = fullSizeData, let fileReference = fileReference { + if let fullSizeData = fullSizeData, let fileReference = fileReference, fullSizeComplete { accountManager.mediaBox.storeResourceData(fileReference.media.resource.id, data: fullSizeData) } return { arguments in @@ -803,7 +803,7 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef |> 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))) + convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), 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) { From 6057bd61212f6c91be995f20837011e91ae2477d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Aug 2019 09:42:28 +0300 Subject: [PATCH 3/8] Cloud themes improvements --- Telegram-iOS/Info.plist | 5 - .../project.pbxproj | 4 + .../Sources/ThemeUpdateManager.swift | 7 + .../Sources/Themes/EditThemeController.swift | 27 +-- .../Themes/ThemePreviewController.swift | 129 +++++++------- .../Themes/ThemePreviewControllerNode.swift | 6 + .../Themes/ThemeSettingsController.swift | 79 ++++++--- .../Themes/ThemeSettingsThemeItem.swift | 155 ++++++----------- .../AccountStateManagementUtils.swift | 4 +- .../TelegramCore/TelegramCore/Themes.swift | 111 +++++++++--- .../TelegramUI/AccountContext.swift | 3 + .../ChatMessageAttachedContentNode.swift | 2 +- .../ChatMessageWebpageBubbleContentNode.swift | 21 ++- .../TelegramUI/OpenChatMessage.swift | 11 +- .../TelegramUI/TelegramRootController.swift | 1 - .../TelegramUI/ThemeUpdateManager.swift | 158 ++++++++++++++++++ .../project.pbxproj | 4 + .../Sources/WallpaperResources.swift | 157 +++++++++++++++-- 18 files changed, 629 insertions(+), 255 deletions(-) create mode 100644 submodules/AccountContext/Sources/ThemeUpdateManager.swift create mode 100644 submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift diff --git a/Telegram-iOS/Info.plist b/Telegram-iOS/Info.plist index 4de303bfb9..0c3c9d00ae 100644 --- a/Telegram-iOS/Info.plist +++ b/Telegram-iOS/Info.plist @@ -8,11 +8,6 @@ en CFBundleDisplayName ${APP_NAME} - CFBundleDocumentTypes - - - - CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIcons diff --git a/submodules/AccountContext/AccountContext_Xcode.xcodeproj/project.pbxproj b/submodules/AccountContext/AccountContext_Xcode.xcodeproj/project.pbxproj index 811ee00cc6..2f1efe6069 100644 --- a/submodules/AccountContext/AccountContext_Xcode.xcodeproj/project.pbxproj +++ b/submodules/AccountContext/AccountContext_Xcode.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 095214F12318D4F5008CDD87 /* ThemeUpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095214F02318D4F5008CDD87 /* ThemeUpdateManager.swift */; }; D03E4228230565790049C28B /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E4227230565790049C28B /* ChatListController.swift */; }; D03E422A230567900049C28B /* ContactMultiselectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E4229230567900049C28B /* ContactMultiselectionController.swift */; }; D03E487C230770000049C28B /* PeerSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E487B230770000049C28B /* PeerSelectionController.swift */; }; @@ -49,6 +50,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 095214F02318D4F5008CDD87 /* ThemeUpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeUpdateManager.swift; sourceTree = ""; }; D03E4227230565790049C28B /* ChatListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = ""; }; D03E4229230567900049C28B /* ContactMultiselectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactMultiselectionController.swift; sourceTree = ""; }; D03E487B230770000049C28B /* PeerSelectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerSelectionController.swift; sourceTree = ""; }; @@ -155,6 +157,7 @@ D0879B6A22F7B04400C4D6B3 /* DeviceContactData.swift */, D0879B6E22F83F6600C4D6B3 /* DownloadedMediaStoreManager.swift */, D0879B7022F8425B00C4D6B3 /* WallpaperUploadManager.swift */, + 095214F02318D4F5008CDD87 /* ThemeUpdateManager.swift */, D0879B7222F842FF00C4D6B3 /* WatchManager.swift */, D0C9C12222FE414700FAB518 /* PresentationSurfaceLevels.swift */, D0C9C3D62300CC2400FAB518 /* FetchMediaUtils.swift */, @@ -271,6 +274,7 @@ D0879B6122F7A7A600C4D6B3 /* UniversalVideoNode.swift in Sources */, D0879A4E22F65B2A00C4D6B3 /* MediaManager.swift in Sources */, D0879A4A22F6584B00C4D6B3 /* SharedMediaPlayer.swift in Sources */, + 095214F12318D4F5008CDD87 /* ThemeUpdateManager.swift in Sources */, D0879B7122F8425B00C4D6B3 /* WallpaperUploadManager.swift in Sources */, D03E422A230567900049C28B /* ContactMultiselectionController.swift in Sources */, D03E487C230770000049C28B /* PeerSelectionController.swift in Sources */, diff --git a/submodules/AccountContext/Sources/ThemeUpdateManager.swift b/submodules/AccountContext/Sources/ThemeUpdateManager.swift new file mode 100644 index 0000000000..1e2f7bef56 --- /dev/null +++ b/submodules/AccountContext/Sources/ThemeUpdateManager.swift @@ -0,0 +1,7 @@ +import Foundation +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData + +public protocol ThemeUpdateManager: class { +} diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index abdb484974..4e8a7dd749 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -132,7 +132,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry { func item(_ arguments: EditThemeControllerArguments) -> ListViewItem { switch self { case let .title(theme, strings, title, text, done): - return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: title, type: .regular(capitalization: true, autocorrection: false), returnKeyType: done ? .done : .next, clearType: .onFocus, tag: EditThemeEntryTag.title, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(), text: text, placeholder: title, type: .regular(capitalization: true, autocorrection: false), returnKeyType: .default, clearType: .onFocus, tag: EditThemeEntryTag.title, sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.title = value @@ -268,9 +268,13 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll 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: .media(media: .standalone(media: file.file), 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 + return wallpaperDatas(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) + |> mapToSignal { _, fullSizeData, complete -> Signal in + guard complete, let fullSizeData = fullSizeData else { + return .complete() + } + context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData) + return .single(wallpaper.wallpaper) } } else { return .single(nil) @@ -422,11 +426,11 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) } let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper)) - var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + var chatWallpaper = current.chatWallpaper if let theme = theme { - themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper + chatWallpaper = resolvedWallpaper ?? 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) + return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) } |> deliverOnMainQueue).start(completed: { if !hasCustomFile { @@ -459,18 +463,15 @@ 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 + var chatWallpaper = current.chatWallpaper if let theme = theme { - themeSpecificChatWallpapers[themeReference.index] = theme.chat.defaultWallpaper + chatWallpaper = resolvedWallpaper ?? 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) + return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: themeReference, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) } |> deliverOnMainQueue).start(completed: { if let themeResource = themeResource, !hasCustomFile { diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 2e0519c277..55b0411bd7 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -139,15 +139,15 @@ public final class ThemePreviewController: ViewController { switch self.source { case .theme, .slug: theme = combineLatest(self.theme.get() |> take(1), wallpaperPromise.get() |> take(1)) - |> map { theme, wallpaper in + |> mapToSignal { theme, wallpaper -> Signal in if let theme = theme { if case let .file(file) = wallpaper, file.id != 0 { - return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)) + return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper))) } else { - return .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)) + return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil))) } } else { - return nil + return .complete() } } case .media: @@ -169,72 +169,73 @@ public final class ThemePreviewController: ViewController { return .complete() } switch theme { - case .cloud: + case let .cloud(info): + resolvedWallpaper = info.resolvedWallpaper return .single(theme) case let .local(info): return wallpaperPromise.get() + |> take(1) + |> mapToSignal { currentWallpaper -> Signal in + if case let .file(file) = currentWallpaper, file.id != 0 { + resolvedWallpaper = currentWallpaper + } + + var wallpaperImage: UIImage? + if case .file = currentWallpaper { + wallpaperImage = chatControllerBackgroundImage(theme: previewTheme, wallpaper: currentWallpaper, 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: previewTheme, wallpaperImage: wallpaperImage, size: size) + })?.cgImage { + context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + } + }, scale: 1.0) + let themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6) + + return telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) |> take(1) - |> mapToSignal { currentWallpaper -> Signal in - if case let .file(file) = currentWallpaper, file.id != 0 { - resolvedWallpaper = currentWallpaper - } - - var wallpaperImage: UIImage? - if case .file = currentWallpaper { - wallpaperImage = chatControllerBackgroundImage(theme: previewTheme, wallpaper: currentWallpaper, 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: previewTheme, wallpaperImage: wallpaperImage, size: size) - })?.cgImage { - context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + |> mapToSignal { themes -> Signal in + let similarTheme = themes.filter { $0.isCreator && $0.title == info.title }.first + if let similarTheme = similarTheme { + return updateTheme(account: context.account, theme: similarTheme, title: nil, slug: nil, resource: info.resource, thumbnailData: themeThumbnailData) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) } - }, scale: 1.0) - let themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6) - - return telegramThemes(postbox: context.account.postbox, network: context.account.network) - |> take(1) - |> mapToSignal { themes -> Signal in - let similarTheme = themes.filter { $0.isCreator && $0.title == info.title }.first - if let similarTheme = similarTheme { - return updateTheme(account: context.account, theme: similarTheme, title: nil, slug: nil, resource: info.resource, thumbnailData: themeThumbnailData) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { result -> Signal in - guard let result = result else { - let updatedTheme = PresentationLocalTheme(title: info.title, resource: info.resource, resolvedWallpaper: resolvedWallpaper) - return .single(.local(updatedTheme)) - } - if case let .result(theme) = result, let file = theme.file { - context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id) - return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper))) - } else { - return .complete() - } - } - - } else { - return createTheme(account: context.account, title: info.title, resource: info.resource, thumbnailData: themeThumbnailData) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { result -> Signal in - guard let result = result else { - let updatedTheme = PresentationLocalTheme(title: info.title, resource: info.resource, resolvedWallpaper: resolvedWallpaper) - return .single(.local(updatedTheme)) - } - if case let .result(updatedTheme) = result, let file = updatedTheme.file { - context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id) - return .single(.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper))) - } else { - return .complete() - } - } + |> mapToSignal { result -> Signal in + guard let result = result else { + let updatedTheme = PresentationLocalTheme(title: info.title, resource: info.resource, resolvedWallpaper: resolvedWallpaper) + return .single(.local(updatedTheme)) } + if case let .result(theme) = result, let file = theme.file { + context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id) + return .single(.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper))) + } else { + return .complete() + } + } + + } else { + return createTheme(account: context.account, title: info.title, resource: info.resource, thumbnailData: themeThumbnailData) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + let updatedTheme = PresentationLocalTheme(title: info.title, resource: info.resource, resolvedWallpaper: resolvedWallpaper) + return .single(.local(updatedTheme)) + } + if case let .result(updatedTheme) = result, let file = updatedTheme.file { + context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id) + return .single(.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper))) + } else { + return .complete() + } + } } + } } case .builtin: return .single(theme) @@ -243,7 +244,7 @@ public final class ThemePreviewController: ViewController { |> mapToSignal { theme -> Signal in if case let .cloud(info) = theme { let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: info.theme).start() - let _ = saveThemeInteractively(account: context.account, theme: info.theme).start() + let _ = saveThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: info.theme).start() } return context.sharedContext.accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index a74f41952d..0555305f43 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -175,6 +175,12 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let fileReference = FileMediaReference.standalone(media: file.file) let signal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: fileReference, representations: convertedRepresentations, alwaysShowThumbnailFirst: false, autoFetchFullSize: false) + |> afterNext { next in + if let _ = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.file.resource) { + } else if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data) + } + } strongSelf.remoteChatBackgroundNode.setSignal(signal) strongSelf.fetchDisposable.set(freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file.file)).start()) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 4b147ac8d1..1f8d4f9ccb 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -8,6 +8,7 @@ import TelegramPresentationData import TelegramUIPreferences import ItemListUI import AlertUI +import WallpaperResources import ShareController import AccountContext @@ -401,31 +402,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The currentAppIconName.set(currentAppIcon?.name ?? "Blue") let cloudThemes = Promise<[TelegramTheme]>() - let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network) + let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) cloudThemes.set(updatedCloudThemes) let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in - 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 - } - - let chatWallpaper: TelegramWallpaper - if let themeSpecificWallpaper = current.themeSpecificChatWallpapers[theme.index] { - chatWallpaper = themeSpecificWallpaper - } else { - let accentColor = current.themeSpecificAccentColors[theme.index]?.color - let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: theme, accentColor: accentColor, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: current.themeSpecificAccentColors[theme.index]?.baseColor ?? .blue) - chatWallpaper = theme.chat.defaultWallpaper - } - - return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) - }) - }).start() + selectThemeImpl?(theme) }, selectFontSize: { size in let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: size, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) @@ -510,7 +491,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The selectThemeImpl?(newTheme) } - let _ = deleteThemeInteractively(account: context.account, theme: theme.theme).start() + let _ = deleteThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme.theme).start() }) })) actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ @@ -585,7 +566,52 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The return controller?.navigationController as? NavigationController } selectThemeImpl = { theme in - arguments.selectTheme(theme) + let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: theme, accentColor: nil, serviceBackgroundColor: .black, baseColor: nil) + + let resolvedWallpaper: Signal + if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { + resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug) + |> map { wallpaper -> TelegramWallpaper? in + return wallpaper?.wallpaper + } + } else { + resolvedWallpaper = .single(nil) + } + + var cloudTheme: TelegramTheme? + if case let .cloud(theme) = theme { + cloudTheme = theme.theme + } + let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: cloudTheme).start() + + let _ = (resolvedWallpaper + |> mapToSignal { resolvedWallpaper -> Signal in + var updatedTheme = theme + if case let .cloud(info) = theme { + updatedTheme = .cloud(PresentationCloudTheme(theme: info.theme, resolvedWallpaper: resolvedWallpaper)) + } + + return (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 + } + + let chatWallpaper: TelegramWallpaper + if let themeSpecificWallpaper = current.themeSpecificChatWallpapers[updatedTheme.index] { + chatWallpaper = themeSpecificWallpaper + } else { + let presentationTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: updatedTheme, accentColor: current.themeSpecificAccentColors[updatedTheme.index]?.color, serviceBackgroundColor: .black, baseColor: nil) + chatWallpaper = resolvedWallpaper ?? presentationTheme.chat.defaultWallpaper + } + + return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: updatedTheme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + }) + }) + }).start() } moreImpl = { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -615,7 +641,7 @@ public final class ThemeSettingsCrossfadeController: ViewController { private let snapshotView: UIView? public init() { - self.snapshotView = UIScreen.main.snapshotView(afterScreenUpdates: false) + self.snapshotView = UIScreen.main.snapshotView(afterScreenUpdates: true) super.init(navigationBarPresentationData: nil) @@ -631,6 +657,7 @@ public final class ThemeSettingsCrossfadeController: ViewController { self.displayNode.backgroundColor = nil self.displayNode.isOpaque = false + self.displayNode.isUserInteractionEnabled = false if let snapshotView = self.snapshotView { self.displayNode.view.addSubview(snapshotView) } @@ -639,7 +666,7 @@ public final class ThemeSettingsCrossfadeController: ViewController { override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in + self.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in self?.presentingViewController?.dismiss(animated: false, completion: nil) }) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift index bac5203446..650577c8c4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift @@ -11,34 +11,43 @@ import ItemListUI import WallpaperResources import AccountContext +private var borderImages: [String: UIImage] = [:] + private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? { - return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in - let bounds = CGRect(origin: CGPoint(), size: size) - context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor) - context.fill(bounds) - - context.setBlendMode(.clear) - context.fillEllipse(in: bounds) - context.setBlendMode(.normal) - - let lineWidth: CGFloat - if selected { - var accentColor = theme.list.itemAccentColor - if accentColor.rgb == 0xffffff { - accentColor = UIColor(rgb: 0x999999) + let key = "\(theme.list.itemBlocksBackgroundColor.hexString)_\(selected ? "s" + theme.list.itemAccentColor.hexString : theme.list.disclosureArrowColor.hexString)" + if let image = borderImages[key] { + return image + } else { + let image = generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor) + context.fill(bounds) + + context.setBlendMode(.clear) + context.fillEllipse(in: bounds) + context.setBlendMode(.normal) + + let lineWidth: CGFloat + if selected { + var accentColor = theme.list.itemAccentColor + if accentColor.rgb == 0xffffff { + accentColor = UIColor(rgb: 0x999999) + } + context.setStrokeColor(accentColor.cgColor) + lineWidth = 2.0 + } else { + context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor) + lineWidth = 1.0 } - context.setStrokeColor(accentColor.cgColor) - lineWidth = 2.0 - } else { - context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor) - lineWidth = 1.0 - } - - if bordered || selected { - context.setLineWidth(lineWidth) - context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)) - } - })?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15) + + if bordered || selected { + context.setLineWidth(lineWidth) + context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)) + } + })?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15) + borderImages[key] = image + return image + } } private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { @@ -66,78 +75,6 @@ private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImag } } -private func themeIconImage(context: AccountContext, theme: PresentationThemeReference, accentColor: UIColor?) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal: Signal<(UIColor, UIColor, UIColor), NoError> - if case let .builtin(theme) = theme { - switch theme { - case .dayClassic: - signal = .single((UIColor(rgb: 0xd6e2ee), UIColor(rgb: 0xffffff), UIColor(rgb: 0xe1ffc7))) - case .day: - signal = .single((.white, UIColor(rgb: 0xd5dde6), accentColor ?? UIColor(rgb: 0x007aff))) - case .night: - signal = .single((.black, UIColor(rgb: 0x1f1f1f), accentColor ?? UIColor(rgb: 0x313131))) - case .nightAccent: - let accentColor = accentColor ?? UIColor(rgb: 0x007aff) - signal = .single((accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18), accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25), accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59))) - } - } else { - var resource: MediaResource? - if case let .local(theme) = theme { - resource = theme.resource - } else if case let .cloud(theme) = theme { - resource = theme.theme.file?.resource - } - if let resource = resource { - signal = telegramThemeData(account: context.account, accountManager: context.sharedContext.accountManager, resource: resource, synchronousLoad: false) - |> mapToSignal { data -> Signal<(UIColor, UIColor, UIColor), NoError> in - if let data = data, let theme = makePresentationTheme(data: data) { - let backgroundColor: UIColor - switch theme.chat.defaultWallpaper { - case .builtin: - backgroundColor = UIColor(rgb: 0xd6e2ee) - case let .color(color): - backgroundColor = UIColor(rgb: UInt32(bitPattern: color)) - default: - backgroundColor = theme.chatList.backgroundColor - } - return .single((backgroundColor, theme.chat.message.incoming.bubble.withoutWallpaper.fill ,theme.chat.message.outgoing.bubble.withoutWallpaper.fill)) - } else { - return .complete() - } - } - } else { - signal = .never() - } - } - return signal - |> map { colors in - return { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) - let drawingRect = arguments.drawingRect - - context.withContext { c in - c.setFillColor(colors.0.cgColor) - c.fill(drawingRect) - - let incoming = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), color: colors.1) - let outgoing = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), color: colors.2) - - c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0) - c.scaleBy(x: 1.0, y: -1.0) - c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0) - - c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0)) - - c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0) - c.scaleBy(x: -1.0, y: 1.0) - c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0) - c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0)) - } - - return context - } - } -} class ThemeSettingsThemeItem: ListViewItem, ItemListItem { var sectionId: ItemListSectionId @@ -206,6 +143,11 @@ private final class ThemeSettingsThemeItemIconNode : ASDisplayNode { private var action: (() -> Void)? private var longTapAction: (() -> Void)? + private var theme: PresentationThemeReference? + private var currentTheme: PresentationTheme? + private var bordered: Bool? + private var selected: Bool? + override init() { self.imageNode = TransformImageNode() self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 98.0, height: 62.0)) @@ -228,12 +170,23 @@ private final class ThemeSettingsThemeItemIconNode : ASDisplayNode { func setup(context: AccountContext, theme: PresentationThemeReference, accentColor: UIColor?, currentTheme: PresentationTheme, title: NSAttributedString, bordered: Bool, selected: Bool, action: @escaping () -> Void, longTapAction: @escaping () -> Void) { if case let .cloud(theme) = theme, theme.theme.file == nil { - self.imageNode.setSignal(createThemeImage(theme: currentTheme)) + if self.currentTheme == nil || currentTheme !== self.currentTheme! { + self.imageNode.setSignal(createThemeImage(theme: currentTheme)) + self.currentTheme = currentTheme + } } else { - self.imageNode.setSignal(themeIconImage(context: context, theme: theme, accentColor: accentColor)) + if theme != self.theme { + self.imageNode.setSignal(themeIconImage(account: context.account, accountManager: context.sharedContext.accountManager, theme: theme, accentColor: accentColor)) + self.theme = theme + } + } + if self.currentTheme == nil || currentTheme !== self.currentTheme! || bordered != self.bordered || selected != self.selected { + self.overlayNode.image = generateBorderImage(theme: currentTheme, bordered: bordered, selected: selected) + self.currentTheme = currentTheme + self.bordered = bordered + self.selected = selected } self.textNode.attributedText = title - self.overlayNode.image = generateBorderImage(theme: currentTheme, bordered: bordered, selected: selected) self.action = { action() } diff --git a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift index af4f8c2a57..d541d33a8a 100644 --- a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift @@ -2955,14 +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 + let _ = 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 }) - } + }.start() } addedIncomingMessageIds.append(contentsOf: addedSecretMessageIds) diff --git a/submodules/TelegramCore/TelegramCore/Themes.swift b/submodules/TelegramCore/TelegramCore/Themes.swift index f7ee62972b..b4d8de7ff7 100644 --- a/submodules/TelegramCore/TelegramCore/Themes.swift +++ b/submodules/TelegramCore/TelegramCore/Themes.swift @@ -33,7 +33,7 @@ private let themeFormat = "ios" private let themeFileExtension = "tgios-theme" #endif -public func telegramThemes(postbox: Postbox, network: Network, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> { +public func telegramThemes(postbox: Postbox, network: Network, accountManager: AccountManager, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> { let fetch: ([TelegramTheme]?, Int32?) -> Signal<[TelegramTheme], NoError> = { current, hash in network.request(Api.functions.account.getThemes(format: themeFormat, hash: hash ?? 0)) |> retryRequest @@ -51,6 +51,19 @@ public func telegramThemes(postbox: Postbox, network: Network, forceUpdate: Bool } } |> mapToSignal { items, hash -> Signal<[TelegramTheme], NoError> in + let _ = accountManager.transaction { transaction in + transaction.updateSharedData(SharedDataKeys.themeSettings, { current in + var updated = current as? ThemeSettings ?? ThemeSettings(currentTheme: nil) + for theme in items { + if theme.id == updated.currentTheme?.id { + updated = ThemeSettings(currentTheme: theme) + break + } + } + return updated + }) + }.start() + return postbox.transaction { transaction -> [TelegramTheme] in var entries: [OrderedItemListEntry] = [] for item in items { @@ -135,7 +148,7 @@ private func checkThemeUpdated(network: Network, theme: TelegramTheme) -> Signal } } -private func saveUnsaveTheme(account: Account, theme: TelegramTheme, unsave: Bool) -> Signal { +private func saveUnsaveTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme, unsave: Bool) -> Signal { return account.postbox.transaction { transaction -> Signal in let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) var items = entries.map { $0.contents as! TelegramTheme } @@ -156,7 +169,7 @@ private func saveUnsaveTheme(account: Account, theme: TelegramTheme, unsave: Boo return .complete() } |> mapToSignal { _ -> Signal in - return telegramThemes(postbox: account.postbox, network: account.network, forceUpdate: true) + return telegramThemes(postbox: account.postbox, network: account.network, accountManager: accountManager, forceUpdate: true) |> take(1) |> mapToSignal { _ -> Signal in return .complete() @@ -426,12 +439,12 @@ public final class ThemeSettings: PreferencesEntry, Equatable { } } -public func saveThemeInteractively(account: Account, theme: TelegramTheme) -> Signal { - return saveUnsaveTheme(account: account, theme: theme, unsave: false) +public func saveThemeInteractively(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { + return saveUnsaveTheme(account: account, accountManager: accountManager, theme: theme, unsave: false) } -public func deleteThemeInteractively(account: Account, theme: TelegramTheme) -> Signal { - return saveUnsaveTheme(account: account, theme: theme, unsave: true) +public func deleteThemeInteractively(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { + return saveUnsaveTheme(account: account, accountManager: accountManager, theme: theme, unsave: true) } public func applyTheme(accountManager: AccountManager, account: Account, theme: TelegramTheme?) -> Signal { @@ -450,46 +463,104 @@ public func applyTheme(accountManager: AccountManager, account: Account, theme: } func managedThemesUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { + let currentTheme = Atomic(value: nil) return accountManager.sharedData(keys: [SharedDataKeys.themeSettings]) - |> mapToSignal { sharedData -> Signal in + |> map { sharedData -> TelegramTheme? in let themeSettings = (sharedData.entries[SharedDataKeys.themeSettings] as? ThemeSettings) ?? ThemeSettings(currentTheme: nil) - if let currentTheme = themeSettings.currentTheme { + return themeSettings.currentTheme + } + |> filter { theme in + return theme?.id != currentTheme.with({ $0 })?.id + } + |> mapToSignal { theme -> Signal in + let _ = currentTheme.swap(theme) + if let theme = theme { let poll = Signal { subscriber in - return checkThemeUpdated(network: network, theme: currentTheme).start(next: { result in + let actualTheme = currentTheme.with { $0 } ?? theme + return checkThemeUpdated(network: network, theme: actualTheme).start(next: { result in if case let .updated(updatedTheme) = result { + let _ = currentTheme.swap(theme) let _ = accountManager.transaction { transaction in transaction.updateSharedData(SharedDataKeys.themeSettings, { _ in return ThemeSettings(currentTheme: updatedTheme) }) }.start() let _ = postbox.transaction { transaction in - - } + let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) + let items = entries.map { entry -> TelegramTheme in + let theme = entry.contents as! TelegramTheme + if theme.id == updatedTheme.id { + return updatedTheme + } else { + return theme + } + } + var updatedEntries: [OrderedItemListEntry] = [] + for item in items { + var intValue = Int32(updatedEntries.count) + let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4)) + updatedEntries.append(OrderedItemListEntry(id: id, contents: item)) + } + transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) + }.start() } subscriber.putCompletion() }) } - return ((.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue())) |> then(poll)) |> restart + return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } else { return .complete() } } } -public func actualizedTheme(accountManager: AccountManager, theme: TelegramTheme) -> Signal { +private func areThemesEqual(_ lhs: TelegramTheme, _ rhs: TelegramTheme) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.slug != rhs.slug { + return false + } + if lhs.file?.id != rhs.file?.id { + return false + } + return true +} + +public func actualizedTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { var currentTheme = theme return accountManager.sharedData(keys: [SharedDataKeys.themeSettings]) - |> map { sharedData -> TelegramTheme in + |> mapToSignal { sharedData -> Signal in let themeSettings = (sharedData.entries[SharedDataKeys.themeSettings] as? ThemeSettings) ?? ThemeSettings(currentTheme: nil) - if let updatedTheme = themeSettings.currentTheme, updatedTheme.id == currentTheme.id { - if updatedTheme != currentTheme { + if let updatedTheme = themeSettings.currentTheme, updatedTheme.id == theme.id { + if !areThemesEqual(updatedTheme, currentTheme) { currentTheme = updatedTheme - return updatedTheme + return .single(updatedTheme) } else { - return currentTheme + return .single(currentTheme) } } else { - return theme + return account.postbox.combinedView(keys: [PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudThemes)]) + |> map { view -> [TelegramTheme] in + if let view = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudThemes)] as? OrderedItemListView { + return view.items.compactMap { $0.contents as? TelegramTheme } + } else { + return [] + } + } + |> map { themes -> TelegramTheme in + let updatedTheme = themes.filter { $0.id == theme.id }.first + if let updatedTheme = updatedTheme { + if !areThemesEqual(updatedTheme, currentTheme) { + currentTheme = updatedTheme + return updatedTheme + } else { + return currentTheme + } + } else { + return currentTheme + } + } } } } diff --git a/submodules/TelegramUI/TelegramUI/AccountContext.swift b/submodules/TelegramUI/TelegramUI/AccountContext.swift index d219e4832f..01a1625fe4 100644 --- a/submodules/TelegramUI/TelegramUI/AccountContext.swift +++ b/submodules/TelegramUI/TelegramUI/AccountContext.swift @@ -111,6 +111,7 @@ public final class AccountContextImpl: AccountContext { public let liveLocationManager: LiveLocationManager? public let wallpaperUploadManager: WallpaperUploadManager? + private let themeUpdateManager: ThemeUpdateManager? public let peerChannelMemberCategoriesContextsManager = PeerChannelMemberCategoriesContextsManager() @@ -143,9 +144,11 @@ public final class AccountContextImpl: AccountContext { if sharedContext.applicationBindings.isMainApp { self.prefetchManager = PrefetchManager(sharedContext: sharedContext, account: account, fetchManager: self.fetchManager) self.wallpaperUploadManager = WallpaperUploadManagerImpl(sharedContext: sharedContext, account: account, presentationData: sharedContext.presentationData) + self.themeUpdateManager = ThemeUpdateManagerImpl(sharedContext: sharedContext, account: account) } else { self.prefetchManager = nil self.wallpaperUploadManager = nil + self.themeUpdateManager = nil } let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration]) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAttachedContentNode.swift index 8422618ad7..4beb03ffe5 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAttachedContentNode.swift @@ -402,7 +402,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if let (media, flags) = mediaAndFlags { if let file = media as? TelegramMediaFile { if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) + let automaticDownload = true let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, file, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index 223f86a833..a9d4ceb999 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -246,7 +246,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { mainMedia = webpage.file ?? webpage.image } - if let file = mainMedia as? TelegramMediaFile { + if let file = mainMedia as? TelegramMediaFile, webpage.type != "telegram_theme" { if let embedUrl = webpage.embedUrl, !embedUrl.isEmpty { if automaticPlayback { mediaAndFlags = (file, [.preferMediaBeforeText]) @@ -293,9 +293,18 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) } } - } else if type == "telegram_theme", let files = webpage.files, let file = files.first { - let media = WallpaperPreviewMedia(content: .file(file, nil, true)) - mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) + } else if type == "telegram_theme" { + var file: TelegramMediaFile? + let mimeType = "application/x-tgtheme-ios" + if let contentFiles = webpage.files, let filteredFile = contentFiles.filter({ $0.mimeType == mimeType }).first { + file = filteredFile + } else if let contentFile = webpage.file, contentFile.mimeType == mimeType { + file = contentFile + } + if let file = file { + let media = WallpaperPreviewMedia(content: .file(file, nil, true)) + mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) + } } } @@ -323,7 +332,9 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { case "telegram_theme": title = item.presentationData.strings.Conversation_Theme text = nil - actionTitle = item.presentationData.strings.Conversation_ViewTheme + if mediaAndFlags != nil { + actionTitle = item.presentationData.strings.Conversation_ViewTheme + } default: break } diff --git a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift index 8856221b1d..285f72a1b5 100644 --- a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift +++ b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift @@ -463,8 +463,15 @@ 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 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) { + var file: TelegramMediaFile? + let mimeType = "application/x-tgtheme-ios" + if let contentFiles = content.files, let filteredFile = contentFiles.filter({ $0.mimeType == mimeType }).first { + file = filteredFile + } else if let contentFile = content.file, contentFile.mimeType == mimeType { + file = contentFile + } + if case let .theme(slug) = resolvedUrl, let file = file { + if 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/TelegramUI/TelegramUI/TelegramRootController.swift b/submodules/TelegramUI/TelegramUI/TelegramRootController.swift index 264e1570c3..2c5e678bdb 100644 --- a/submodules/TelegramUI/TelegramUI/TelegramRootController.swift +++ b/submodules/TelegramUI/TelegramUI/TelegramRootController.swift @@ -45,7 +45,6 @@ public final class TelegramRootController: NavigationController { self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { - if presentationData.chatWallpaper != strongSelf.presentationData.chatWallpaper { let navigationDetailsBackgroundMode: NavigationEmptyDetailsBackgoundMode? switch presentationData.chatWallpaper { diff --git a/submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift b/submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift new file mode 100644 index 0000000000..2e2c6d9512 --- /dev/null +++ b/submodules/TelegramUI/TelegramUI/ThemeUpdateManager.swift @@ -0,0 +1,158 @@ +import Foundation +import UIKit +import Postbox +import TelegramCore +import SwiftSignalKit +import TelegramPresentationData +import TelegramUIPreferences +import MediaResources +import WallpaperResources +import AccountContext + +private final class ThemeUpdateManagerContext { + let themeReference: PresentationThemeReference + private let disposable: Disposable + let isAutoNight: Bool + + init(themeReference: PresentationThemeReference, disposable: Disposable, isAutoNight: Bool) { + self.themeReference = themeReference + self.disposable = disposable + self.isAutoNight = isAutoNight + } + + deinit { + self.disposable.dispose() + } +} + +final class ThemeUpdateManagerImpl: ThemeUpdateManager { + private let sharedContext: SharedAccountContext + private let account: Account + private var contexts: [Int64: ThemeUpdateManagerContext] = [:] + private let queue = Queue() + + private var disposable: Disposable? + private var currentThemeSettings: PresentationThemeSettings? + + init(sharedContext: SharedAccountContext, account: Account) { + self.sharedContext = sharedContext + self.account = account + + self.disposable = (sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) + |> map { sharedData -> PresentationThemeSettings in + return (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings + } + |> deliverOn(queue)).start(next: { [weak self] themeSettings in + self?.presentationThemeSettingsUpdated(themeSettings) + }) + } + + deinit { + self.disposable?.dispose() + } + + private func presentationThemeSettingsUpdated(_ themeSettings: PresentationThemeSettings) { + let previousThemeSettings = self.currentThemeSettings + self.currentThemeSettings = themeSettings + + var previousIds = Set() + if let previousThemeSettings = previousThemeSettings { + previousIds.insert(previousThemeSettings.theme.index) + } + + var validIds = Set() + var themes: [Int64: (PresentationThemeReference, Bool)] = [:] + if case .cloud = themeSettings.theme { + validIds.insert(themeSettings.theme.index) + themes[themeSettings.theme.index] = (themeSettings.theme, false) + } + + if previousIds != validIds { + for id in validIds { + if let _ = self.contexts[id] { + } else if let (theme, isAutoNight) = themes[id], case let .cloud(info) = theme { + var currentTheme = theme + let account = self.account + let accountManager = self.sharedContext.accountManager + let disposable = (actualizedTheme(account: account, accountManager: accountManager, theme: info.theme) + |> mapToSignal { theme -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in + guard let file = theme.file else { + return .complete() + } + return telegramThemeData(account: account, accountManager: accountManager, resource: file.resource) + |> mapToSignal { data -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in + guard let data = data, let presentationTheme = makePresentationTheme(data: data) else { + return .complete() + } + + let resolvedWallpaper: Signal + if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { + resolvedWallpaper = cachedWallpaper(account: account, slug: file.slug) + |> map { wallpaper in + return wallpaper?.wallpaper + } + } else { + resolvedWallpaper = .single(nil) + } + + return resolvedWallpaper + |> mapToSignal { wallpaper -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in + if let wallpaper = wallpaper, case let .file(file) = wallpaper { + var convertedRepresentations: [ImageRepresentationWithReference] = [] + convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: CGSize(width: 100.0, height: 100.0), resource: file.file.resource), reference: .media(media: .standalone(media: file.file), resource: file.file.resource))) + return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) + |> mapToSignal { _, fullSizeData, complete -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in + guard complete, let fullSizeData = fullSizeData else { + return .complete() + } + accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData) + return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper)), presentationTheme)) + } + } else { + return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil)), presentationTheme)) + } + } + } + }).start(next: { updatedTheme, presentationTheme in + if updatedTheme != currentTheme { + currentTheme = updatedTheme + + let _ = (accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in + let current: PresentationThemeSettings + if let entry = entry as? PresentationThemeSettings { + current = entry + } else { + current = PresentationThemeSettings.defaultSettings + } + + let chatWallpaper: TelegramWallpaper + if let themeSpecificWallpaper = current.themeSpecificChatWallpapers[updatedTheme.index] { + chatWallpaper = themeSpecificWallpaper + } else if let presentationTheme = presentationTheme { + if case let .cloud(info) = updatedTheme, let resolvedWallpaper = info.resolvedWallpaper { + chatWallpaper = resolvedWallpaper + } else { + chatWallpaper = presentationTheme.chat.defaultWallpaper + } + } else { + chatWallpaper = current.chatWallpaper + } + + return PresentationThemeSettings(chatWallpaper: chatWallpaper, theme: updatedTheme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + }) + }).start() + } + }) + self.contexts[id] = ThemeUpdateManagerContext(themeReference: theme, disposable: disposable, isAutoNight: isAutoNight) + } + } + + for id in previousIds { + if !validIds.contains(id) { + self.contexts[id] = nil + } + } + } + } +} diff --git a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj index ad63274493..909c30af3e 100644 --- a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj +++ b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 094735192277483C00EA2312 /* anim_infotip.json in Resources */ = {isa = PBXBuildFile; fileRef = 094735182277483B00EA2312 /* anim_infotip.json */; }; 09510B1322F96E5B0078CAB7 /* ChatScheduleTimeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09510B1222F96E5B0078CAB7 /* ChatScheduleTimeController.swift */; }; 09510B1522F96E6C0078CAB7 /* ChatScheduleTimeControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09510B1422F96E6C0078CAB7 /* ChatScheduleTimeControllerNode.swift */; }; + 095214EF2318D4D3008CDD87 /* ThemeUpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095214EE2318D4D3008CDD87 /* ThemeUpdateManager.swift */; }; 0962E67921B67A9800245FD9 /* ChatMessageAnimatedStickerItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E67821B67A9800245FD9 /* ChatMessageAnimatedStickerItemNode.swift */; }; 09749BC521F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */; }; 09874E4F21078FA100E190B8 /* Generic.html in Resources */ = {isa = PBXBuildFile; fileRef = 0979788321065F8C0077D77F /* Generic.html */; }; @@ -596,6 +597,7 @@ 094735182277483B00EA2312 /* anim_infotip.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_infotip.json; sourceTree = ""; }; 09510B1222F96E5B0078CAB7 /* ChatScheduleTimeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScheduleTimeController.swift; sourceTree = ""; }; 09510B1422F96E6C0078CAB7 /* ChatScheduleTimeControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScheduleTimeControllerNode.swift; sourceTree = ""; }; + 095214EE2318D4D3008CDD87 /* ThemeUpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeUpdateManager.swift; sourceTree = ""; }; 0962E67821B67A9800245FD9 /* ChatMessageAnimatedStickerItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageAnimatedStickerItemNode.swift; sourceTree = ""; }; 09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersChatInputContextPanelItem.swift; sourceTree = ""; }; 0979788021065F8B0077D77F /* VimeoUserScript.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = VimeoUserScript.js; sourceTree = ""; }; @@ -2540,6 +2542,7 @@ D0F3A8AA1E82D83E00B4C64C /* TelegramAccountAuxiliaryMethods.swift */, D01DBA9A209CC6AD00C64E64 /* ChatLinkPreview.swift */, 090B48C72200BCA8005083FA /* WallpaperUploadManager.swift */, + 095214EE2318D4D3008CDD87 /* ThemeUpdateManager.swift */, 09D96898221DE92600B1458A /* ID3ArtworkReader.swift */, D099E21F229405BB00561B75 /* Weak.swift */, ); @@ -3053,6 +3056,7 @@ D0EC6DEC1EB9F58900EBF1C3 /* ChatToastAlertPanelNode.swift in Sources */, D0EC6DED1EB9F58900EBF1C3 /* ChatHistoryNavigationButtonNode.swift in Sources */, D0EC6DF51EB9F58900EBF1C3 /* PeerMediaCollectionController.swift in Sources */, + 095214EF2318D4D3008CDD87 /* ThemeUpdateManager.swift in Sources */, D0EC6DF61EB9F58900EBF1C3 /* PeerMediaCollectionControllerNode.swift in Sources */, D0EC6DF81EB9F58900EBF1C3 /* PeerMediaCollectionInterfaceState.swift in Sources */, D0EC6DF91EB9F58900EBF1C3 /* PeerMediaCollectionInterfaceStateButtons.swift in Sources */, diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index a2488a3c28..29075a0ec9 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -10,8 +10,9 @@ import TinyThumbnail import PhotoResources import LocalMediaResources import TelegramPresentationData +import TelegramUIPreferences -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> { +public 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 @@ -51,7 +52,13 @@ private func wallpaperDatas(account: Account, accountManager: AccountManager, fi } else { maybeFullSize = combineLatest(accountManager.mediaBox.resourceData(largestRepresentation.resource), account.postbox.mediaBox.resourceData(largestRepresentation.resource)) |> map { sharedData, data -> MediaResourceData in - if sharedData.complete { + if sharedData.complete && data.complete { + if sharedData.size > data.size { + return sharedData + } else { + return data + } + } else if sharedData.complete { return sharedData } else { return data @@ -168,9 +175,6 @@ public func wallpaperImage(account: Account, accountManager: AccountManager, fil return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in - if let fullSizeData = fullSizeData, let fileReference = fileReference, fullSizeComplete { - accountManager.mediaBox.storeResourceData(fileReference.media.resource.id, data: fullSizeData) - } return { arguments in let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize @@ -259,7 +263,6 @@ public func wallpaperImage(account: Account, accountManager: AccountManager, fil } let context = DrawingContext(size: arguments.drawingSize, clear: true) - context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { @@ -278,9 +281,7 @@ public func wallpaperImage(account: Account, accountManager: AccountManager, fil drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect) } } - addCorners(context, arguments: arguments) - return context } } @@ -596,7 +597,7 @@ public func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryM } } -public func telegramThemeData(account: Account, accountManager: AccountManager, resource: MediaResource, synchronousLoad: Bool) -> Signal { +public func telegramThemeData(account: Account, accountManager: AccountManager, resource: MediaResource, synchronousLoad: Bool = false) -> Signal { let maybeFetched = accountManager.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) return maybeFetched |> take(1) @@ -653,7 +654,7 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallp case let .color(value): c.setFillColor(UIColor(rgb: UInt32(bitPattern: value)).cgColor) c.fill(drawingRect) - case let .file(file): + case .file: c.setFillColor(theme.chatList.backgroundColor.cgColor) c.fill(drawingRect) @@ -804,12 +805,17 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef 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: .media(media: .standalone(media: file.file), 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) + return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) + |> mapToSignal { _, fullSizeData, complete -> Signal<(PresentationTheme?, UIImage?, Data?), NoError> in + guard complete, let fullSizeData = fullSizeData else { + return .complete() + } + accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData) + + if let image = UIImage(data: fullSizeData) { + return .single((theme, image, thumbnailData)) } else { - return (theme, nil, thumbnailData) + return .single((theme, nil, thumbnailData)) } } } else { @@ -890,3 +896,124 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef } } } + +public func themeIconImage(account: Account, accountManager: AccountManager, theme: PresentationThemeReference, accentColor: UIColor?) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let signal: Signal<(UIColor, UIColor, UIColor, UIImage?), NoError> + if case let .builtin(theme) = theme { + switch theme { + case .dayClassic: + signal = .single((UIColor(rgb: 0xd6e2ee), UIColor(rgb: 0xffffff), UIColor(rgb: 0xe1ffc7), nil)) + case .day: + signal = .single((.white, UIColor(rgb: 0xd5dde6), accentColor ?? UIColor(rgb: 0x007aff), nil)) + case .night: + signal = .single((.black, UIColor(rgb: 0x1f1f1f), accentColor ?? UIColor(rgb: 0x313131), nil)) + case .nightAccent: + let accentColor = accentColor ?? UIColor(rgb: 0x007aff) + signal = .single((accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18), accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25), accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59), nil)) + } + } else { + var resource: MediaResource? + if case let .local(theme) = theme { + resource = theme.resource + } else if case let .cloud(theme) = theme { + resource = theme.theme.file?.resource + } + if let resource = resource { + signal = telegramThemeData(account: account, accountManager: accountManager, resource: resource, synchronousLoad: false) + |> mapToSignal { data -> Signal<(UIColor, UIColor, UIColor, UIImage?), NoError> in + if let data = data, let theme = makePresentationTheme(data: data) { + var wallpaperSignal: Signal<(UIColor, UIColor, UIColor, UIImage?), NoError> = .complete() + let backgroundColor: UIColor + let incomingColor = theme.chat.message.incoming.bubble.withoutWallpaper.fill + let outgoingColor = theme.chat.message.outgoing.bubble.withoutWallpaper.fill + switch theme.chat.defaultWallpaper { + case .builtin: + backgroundColor = UIColor(rgb: 0xd6e2ee) + case let .color(color): + backgroundColor = UIColor(rgb: UInt32(bitPattern: color)) + case .image: + backgroundColor = .black + case let .file(file): + backgroundColor = theme.chatList.backgroundColor + wallpaperSignal = cachedWallpaper(account: account, slug: file.slug) + |> mapToSignal { wallpaper 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: .media(media: .standalone(media: file.file), resource: file.file.resource))) + return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) + |> mapToSignal { _, fullSizeData, complete -> Signal<(UIColor, UIColor, UIColor, UIImage?), NoError> in + guard complete, let fullSizeData = fullSizeData else { + return .complete() + } + accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData) + + if let image = UIImage(data: fullSizeData) { + return .single((backgroundColor, incomingColor, outgoingColor, image)) + } else { + return .complete() + } + } + } else { + return .complete() + } + } + } + return .single((backgroundColor, incomingColor, outgoingColor, nil)) + |> then(wallpaperSignal) + } else { + return .complete() + } + } + } else { + signal = .never() + } + } + return signal + |> map { colors in + return { arguments in + let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) + let drawingRect = arguments.drawingRect + + context.withContext { c in + c.setFillColor(colors.0.cgColor) + c.fill(drawingRect) + + if let image = colors.3 { + let initialThumbnailContextFittingSize = arguments.imageSize.fitted(CGSize(width: 90.0, height: 90.0)) + let thumbnailContextSize = image.size.aspectFilled(initialThumbnailContextFittingSize) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + if let blurredThumbnailImage = thumbnailContext.generateImage(), let cgImage = blurredThumbnailImage.cgImage { + let fittedSize = thumbnailContext.size.aspectFilled(CGSize(width: drawingRect.size.width + 1.0, height: drawingRect.size.height + 1.0)) + c.saveGState() + c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0) + c.scaleBy(x: 1.0, y: -1.0) + c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0) + c.draw(cgImage, in: CGRect(origin: CGPoint(x: (drawingRect.size.width - fittedSize.width) / 2.0, y: (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)) + c.restoreGState() + } + } + + let incoming = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), color: colors.1) + let outgoing = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), color: colors.2) + + c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0) + c.scaleBy(x: 1.0, y: -1.0) + c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0) + + c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0)) + + c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0) + c.scaleBy(x: -1.0, y: 1.0) + c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0) + c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0)) + } + + return context + } + } +} From 86050690bf39bc528b869fd81a2d47fc0ab4ad2a Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 30 Aug 2019 12:39:14 +0400 Subject: [PATCH 4/8] Remove testing code --- submodules/TelegramUI/TelegramUI/ChatControllerNode.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift index 1e74472792..5495db5a4a 100644 --- a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift @@ -2086,13 +2086,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var messages: [EnqueueMessage] = [] - var inputText = convertMarkdownToAttributes(effectivePresentationInterfaceState.interfaceState.composeInputState.inputText) - - let tempString = NSMutableAttributedString() - let s = "\u{202e}" - tempString.append(NSAttributedString(string: s)) - tempString.append(inputText) - inputText = tempString + let inputText = convertMarkdownToAttributes(effectivePresentationInterfaceState.interfaceState.composeInputState.inputText) for text in breakChatInputText(trimChatInputText(inputText)) { if text.length != 0 { From dca4646cf078f5c0e3d7407082388cf5cbe1930f Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 30 Aug 2019 13:16:23 +0400 Subject: [PATCH 5/8] Dismiss context menu after deleting unsent message --- submodules/TelegramUI/TelegramUI/ChatController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 666fd04d13..86d9954aef 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -1428,6 +1428,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: [id], type: .forLocalPeer).start() } + f(.dismissWithoutContent) }))) let controller = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: ChatMessageContextControllerContentSource(chatNode: strongSelf.chatDisplayNode, message: message), items: actions, reactionItems: [], recognizer: nil) From 4d71ae195fb42349d65ca26b57870780795fbdb2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Aug 2019 12:26:43 +0300 Subject: [PATCH 6/8] Reset theme-specific wallpaper on repeated save Show theme-default wallpaper in wallpapers list --- .../Sources/Themes/ThemeGridController.swift | 41 ++++++------------- .../Themes/ThemeGridControllerNode.swift | 16 +++++++- .../Themes/ThemePreviewController.swift | 4 +- .../Sources/WallpaperResources.swift | 2 +- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index e8a2c66b8a..c5b352c669 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -177,23 +177,14 @@ final class ThemeGridController: ViewController { } for wallpaper in wallpapers { if wallpaper == strongSelf.presentationData.chatWallpaper { + let presentationData = strongSelf.presentationData let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in - var fallbackWallpaper: TelegramWallpaper = .builtin(WallpaperSettings()) - if case let .builtin(theme) = current.theme { - switch theme { - case .day: - fallbackWallpaper = .color(0xffffff) - case .night: - fallbackWallpaper = .color(0x000000) - case .nightAccent: - fallbackWallpaper = .color(0x18222d) - default: - fallbackWallpaper = .builtin(WallpaperSettings()) - } + var fallbackWallpaper = presentationData.theme.chat.defaultWallpaper + if case let .cloud(info) = current.theme, let resolvedWallpaper = info.resolvedWallpaper { + fallbackWallpaper = resolvedWallpaper } - var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - themeSpecificChatWallpapers[current.theme.index] = fallbackWallpaper + themeSpecificChatWallpapers[current.theme.index] = nil return PresentationThemeSettings(chatWallpaper: fallbackWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) })).start() break @@ -243,6 +234,7 @@ final class ThemeGridController: ViewController { strongSelf.present(controller, in: .window(.root)) let _ = resetWallpapers(account: strongSelf.context.account).start(completed: { [weak self, weak controller] in + let presentationData = strongSelf.presentationData let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in let current: PresentationThemeSettings @@ -251,22 +243,13 @@ final class ThemeGridController: ViewController { } else { current = PresentationThemeSettings.defaultSettings } - let wallpaper: TelegramWallpaper - if case let .builtin(theme) = current.theme { - switch theme { - case .day: - wallpaper = .color(0xffffff) - case .night: - wallpaper = .color(0x000000) - case .nightAccent: - wallpaper = .color(0x18222d) - default: - wallpaper = .builtin(WallpaperSettings()) - } - } else { - wallpaper = .builtin(WallpaperSettings()) + var fallbackWallpaper = presentationData.theme.chat.defaultWallpaper + if case let .cloud(info) = current.theme, let resolvedWallpaper = info.resolvedWallpaper { + fallbackWallpaper = resolvedWallpaper } - return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: [:], fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + themeSpecificChatWallpapers[current.theme.index] = nil + return PresentationThemeSettings(chatWallpaper: fallbackWallpaper, theme: current.theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: [:], fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) }).start() diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift index 3334ad3b97..fce443bb25 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift @@ -366,6 +366,16 @@ final class ThemeGridControllerNode: ASDisplayNode { entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0) + var defaultWallpaper: TelegramWallpaper? + if !areWallpapersEqual(presentationData.chatWallpaper, presentationData.theme.chat.defaultWallpaper) { + if case .builtin = presentationData.theme.chat.defaultWallpaper { + } else { + defaultWallpaper = presentationData.theme.chat.defaultWallpaper + entries.insert(ThemeGridControllerEntry(index: 1, wallpaper: presentationData.theme.chat.defaultWallpaper, selected: false), at: 1) + index += 1 + } + } + var sortedWallpapers: [TelegramWallpaper] = [] if presentationData.theme.overallDarkAppearance { var darkWallpapers: [TelegramWallpaper] = [] @@ -386,7 +396,11 @@ final class ThemeGridControllerNode: ASDisplayNode { continue } let selected = areWallpapersEqual(presentationData.chatWallpaper, wallpaper) - if !selected { + var isDefault = false + if let defaultWallpaper = defaultWallpaper, areWallpapersEqual(defaultWallpaper, wallpaper) { + isDefault = true + } + if !selected && !isDefault { entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: false)) } index += 1 diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 55b0411bd7..2729fd4a86 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -249,7 +249,9 @@ public final class ThemePreviewController: ViewController { return context.sharedContext.accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in let current = entry as? PresentationThemeSettings ?? PresentationThemeSettings.defaultSettings - return PresentationThemeSettings(chatWallpaper: resolvedWallpaper ?? previewTheme.chat.defaultWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + themeSpecificChatWallpapers[theme.index] = nil + return PresentationThemeSettings(chatWallpaper: resolvedWallpaper ?? previewTheme.chat.defaultWallpaper, theme: theme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, disableAnimations: current.disableAnimations) }) } } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 29075a0ec9..02bcd1d806 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -851,7 +851,7 @@ public func themeImage(account: Account, accountManager: AccountManager, fileRef c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - + 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)) From 119e9f8299384b64994507f1019894b24c028451 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Aug 2019 13:54:00 +0300 Subject: [PATCH 7/8] Refresh applied theme when updating it locally [skip ci] --- .../Sources/Themes/EditThemeController.swift | 2 +- .../Themes/ThemePreviewController.swift | 2 +- .../TelegramCore/TelegramCore/Themes.swift | 19 ++++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index 4e8a7dd749..0a10f198de 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -451,7 +451,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll }) } case let .edit(info): - let _ = (updateTheme(account: context.account, theme: info.theme, title: state.title, slug: state.slug, resource: themeResource) + let _ = (updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, 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() diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 2729fd4a86..ac0fe94c6d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -198,7 +198,7 @@ public final class ThemePreviewController: ViewController { |> mapToSignal { themes -> Signal in let similarTheme = themes.filter { $0.isCreator && $0.title == info.title }.first if let similarTheme = similarTheme { - return updateTheme(account: context.account, theme: similarTheme, title: nil, slug: nil, resource: info.resource, thumbnailData: themeThumbnailData) + return updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: similarTheme, title: nil, slug: nil, resource: info.resource, thumbnailData: themeThumbnailData) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/TelegramCore/Themes.swift b/submodules/TelegramCore/TelegramCore/Themes.swift index b4d8de7ff7..e1e9c2d017 100644 --- a/submodules/TelegramCore/TelegramCore/Themes.swift +++ b/submodules/TelegramCore/TelegramCore/Themes.swift @@ -329,7 +329,7 @@ public func createTheme(account: Account, title: String, resource: MediaResource } } -public func updateTheme(account: Account, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil) -> Signal { +public func updateTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil) -> Signal { guard title != nil || slug != nil || resource != nil else { return .complete() } @@ -379,13 +379,22 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?, return .generic } |> mapToSignal { apiTheme -> Signal in - if let result = TelegramTheme(apiTheme: apiTheme) { + if let updatedTheme = TelegramTheme(apiTheme: apiTheme) { + let _ = accountManager.transaction { transaction in + transaction.updateSharedData(SharedDataKeys.themeSettings, { current in + var updated = current as? ThemeSettings ?? ThemeSettings(currentTheme: nil) + if updatedTheme.id == updated.currentTheme?.id { + updated = ThemeSettings(currentTheme: updatedTheme) + } + return updated + }) + }.start() return account.postbox.transaction { transaction -> CreateThemeResult in let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) let items = entries.map { entry -> TelegramTheme in let theme = entry.contents as! TelegramTheme - if theme.id == result.id { - return result + if theme.id == updatedTheme.id { + return updatedTheme } else { return theme } @@ -397,7 +406,7 @@ public func updateTheme(account: Account, theme: TelegramTheme, title: String?, updatedEntries.append(OrderedItemListEntry(id: id, contents: item)) } transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) - return .result(result) + return .result(updatedTheme) } |> introduceError(CreateThemeError.self) } else { From 8060239d6f62f8a32bf4e305848dacbba8c718cd Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 30 Aug 2019 18:22:51 +0400 Subject: [PATCH 8/8] Move phone discovery description when changing settings --- .../SelectivePrivacySettingsController.swift | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 0b573bcd82..43ffeb053a 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -108,6 +108,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case phoneDiscoveryHeader(PresentationTheme, String) case phoneDiscoveryEverybody(PresentationTheme, String, Bool) case phoneDiscoveryMyContacts(PresentationTheme, String, Bool) + case phoneDiscoveryInfo(PresentationTheme, String) var section: ItemListSectionId { switch self { @@ -123,7 +124,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return SelectivePrivacySettingsSection.callsP2PPeers.rawValue case .callsIntegrationEnabled, .callsIntegrationInfo: return SelectivePrivacySettingsSection.callsIntegrationEnabled.rawValue - case .phoneDiscoveryHeader, .phoneDiscoveryEverybody, .phoneDiscoveryMyContacts: + case .phoneDiscoveryHeader, .phoneDiscoveryEverybody, .phoneDiscoveryMyContacts, .phoneDiscoveryInfo: return SelectivePrivacySettingsSection.phoneDiscovery.rawValue } } @@ -150,34 +151,36 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return 8 case .phoneDiscoveryMyContacts: return 9 - case .exceptionsHeader: + case .phoneDiscoveryInfo: return 10 - case .disableFor: + case .exceptionsHeader: return 11 - case .enableFor: + case .disableFor: return 12 - case .peersInfo: + case .enableFor: return 13 - case .callsP2PHeader: + case .peersInfo: return 14 - case .callsP2PAlways: + case .callsP2PHeader: return 15 - case .callsP2PContacts: + case .callsP2PAlways: return 16 - case .callsP2PNever: + case .callsP2PContacts: return 17 - case .callsP2PInfo: + case .callsP2PNever: return 18 - case .callsP2PDisableFor: + case .callsP2PInfo: return 19 - case .callsP2PEnableFor: + case .callsP2PDisableFor: return 20 - case .callsP2PPeersInfo: + case .callsP2PEnableFor: return 21 - case .callsIntegrationEnabled: + case .callsP2PPeersInfo: return 22 - case .callsIntegrationInfo: + case .callsIntegrationEnabled: return 23 + case .callsIntegrationInfo: + return 24 } } @@ -327,6 +330,12 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } else { return false } + case let .phoneDiscoveryInfo(lhsTheme, lhsText): + if case let .phoneDiscoveryInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } } } @@ -383,7 +392,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { arguments.updateCallP2PMode?(.nobody) }) case let .callsP2PInfo(theme, text): - return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) + return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) case let .callsP2PDisableFor(theme, title, value): return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: { arguments.openSelective(.callP2P, false) @@ -410,6 +419,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updatePhoneDiscovery?(false) }) + case let .phoneDiscoveryInfo(theme, text): + return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) } } } @@ -522,7 +533,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present var entries: [SelectivePrivacySettingsEntry] = [] let settingTitle: String - let settingInfoText: String + let settingInfoText: String? let disableForText: String let enableForText: String switch kind { @@ -554,7 +565,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present case .phoneNumber: settingTitle = presentationData.strings.PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber if state.setting == .nobody, state.phoneDiscoveryEnabled == false { - settingInfoText = presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp + settingInfoText = nil } else { settingInfoText = presentationData.strings.PrivacyPhoneNumberSettings_CustomHelp } @@ -590,12 +601,15 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present case .groupInvitations, .profilePhoto: break } - entries.append(.settingInfo(presentationData.theme, settingInfoText)) + if let settingInfoText = settingInfoText { + entries.append(.settingInfo(presentationData.theme, settingInfoText)) + } if case .phoneNumber = kind, state.setting == .nobody { entries.append(.phoneDiscoveryHeader(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_DiscoveryHeader)) entries.append(.phoneDiscoveryEverybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.phoneDiscoveryEnabled != false)) entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false)) + entries.append(.phoneDiscoveryInfo(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp)) } entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions))