From d423f9068161ac598502be85018e736b1914820f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 22 Aug 2021 18:30:49 +0300 Subject: [PATCH 1/4] Implemented chat-specific themes --- Telegram/BUILD | 19 + .../Telegram-iOS/en.lproj/Localizable.strings | 15 + .../Sources/AccountContext.swift | 7 +- .../AnimationUI/Sources/AnimationNode.swift | 2 + .../Sources/CallListController.swift | 2 +- .../Sources/ContactsController.swift | 2 +- .../ListViewReorderingGestureRecognizer.swift | 3 + submodules/Display/Source/NavigationBar.swift | 2 +- .../Sources/InstantPageControllerNode.swift | 2 +- .../Sources/LegacyAttachmentMenu.swift | 4 +- .../Sources/SecureIdAuthController.swift | 2 +- .../Sources/ChannelBlacklistController.swift | 2 +- .../Sources/ChannelMembersController.swift | 4 +- .../ChannelPermissionsController.swift | 2 +- .../Sources/PeersNearbyController.swift | 2 +- .../BlockedPeersController.swift | 2 +- ...ectivePrivacySettingsPeersController.swift | 2 +- .../Sources/GroupStatsController.swift | 2 +- submodules/TelegramApi/Sources/Api0.swift | 1 + submodules/TelegramApi/Sources/Api2.swift | 12 + .../Sources/VoiceChatController.swift | 11 - .../Sources/Account/AccountManager.swift | 2 + .../ApiUtils/TelegramMediaAction.swift | 4 +- .../TelegramCore/Sources/ApiUtils/Theme.swift | 2 +- .../State/AccountStateManagementUtils.swift | 4 +- .../State/ManagedLocalInputActivities.swift | 2 +- .../Sources/State/PeerInputActivity.swift | 2 + .../SyncCore/SyncCore_CachedChannelData.swift | 67 +- .../SyncCore/SyncCore_CachedGroupData.swift | 51 +- .../SyncCore/SyncCore_CachedUserData.swift | 42 +- .../SyncCore/SyncCore_Namespaces.swift | 7 + .../SyncCore_TelegramMediaAction.swift | 6 + .../Localization/Countries.swift | 1 - .../Peers/UpdateCachedPeerData.swift | 3 + .../TelegramEngine/TelegramEngine.swift | 4 + .../TelegramEngine/Themes/ChatThemes.swift | 153 +++ .../Themes/TelegramEngineThemes.swift | 20 + submodules/TelegramCore/Sources/Themes.swift | 132 ++- .../Sources/DefaultDayPresentationTheme.swift | 2 +- .../Sources/PresentationData.swift | 4 + .../Sources/ServiceMessageStrings.swift | 18 + .../Resources/Animations/anim_sun.json | 1 + .../Animations/anim_sun_reverse.json | 1 + .../TelegramUI/Sources/ChatController.swift | 137 ++- .../Sources/ChatControllerNode.swift | 2 +- .../Sources/ChatHistoryListNode.swift | 6 +- .../ChatMediaInputStickerPackItem.swift | 6 +- .../Sources/ChatMessageBackground.swift | 10 +- .../ChatRecentActionsControllerNode.swift | 4 +- .../TelegramUI/Sources/ChatThemeScreen.swift | 918 ++++++++++++++++++ .../Sources/NavigateToChatController.swift | 6 + .../TelegramUI/Sources/OpenAddContact.swift | 2 +- submodules/TelegramUI/Sources/OpenUrl.swift | 4 +- .../OverlayAudioPlayerControllerNode.swift | 4 +- .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 51 +- .../Sources/PollResultsController.swift | 2 +- .../Sources/SharedAccountContext.swift | 14 +- .../Sources/TelegramRootController.swift | 2 +- .../TelegramUI/Sources/TextLinkHandling.swift | 2 +- .../TooltipUI/Sources/TooltipScreen.swift | 13 +- .../Sources/WallpaperResources.swift | 68 +- .../Sources/WidgetSetupScreen.swift | 2 +- 63 files changed, 1649 insertions(+), 234 deletions(-) create mode 100644 submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift create mode 100644 submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift create mode 100644 submodules/TelegramUI/Resources/Animations/anim_sun.json create mode 100644 submodules/TelegramUI/Resources/Animations/anim_sun_reverse.json create mode 100644 submodules/TelegramUI/Sources/ChatThemeScreen.swift diff --git a/Telegram/BUILD b/Telegram/BUILD index 90d9a181f1..1e41387b9b 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -6,6 +6,8 @@ load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_extension", "ios_framework", + "ios_unit_test", + "ios_ui_test", ) load("@build_bazel_rules_apple//apple:resources.bzl", @@ -1853,6 +1855,23 @@ ios_application( ], ) +swift_library( + name = "UITestsLib", + module_name = "UITestsLib", + srcs = glob(["UITests/**/*.swift"]), + deps = [ + "//submodules/TelegramUI:TelegramUI", + ], +) + +ios_ui_test( + name = "UITests", + minimum_os_version = "9.0", + deps = [":UITestsLib"], + infoplists = [":UITestsLib/Info.plist"], + test_host = ":Telegram", +) + # Temporary targets used to simplify webrtc build tests ios_application( diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b6b30bc99d..237ffaaca7 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -6622,6 +6622,21 @@ Sorry for the inconvenience."; "Group.Username.InvalidStartsWithUnderscore" = "Group names can't start with an underscore."; "Group.Username.InvalidEndsWithUnderscore" = "Group names can't end with an underscore."; +"UserInfo.ChangeColors" = "Change Colors"; + +"Conversation.Theme.Title" = "Select Theme"; +"Conversation.Theme.Apply" = "Apply Theme"; +"Conversation.Theme.NoTheme" = "No\nTheme"; +"Conversation.Theme.Reset" = "Reset Theme for This Chat"; +"Conversation.Theme.SwitchToDark" = "Switch to dark appearance"; +"Conversation.Theme.SwitchToLight" = "Switch to light appearance"; + +"Notification.ChangedTheme" = "%1$@ changed chat theme to %2$@"; +"Notification.DisabledTheme" = "%@ disabled chat theme"; + +"Notification.YouChangedTheme" = "You changed chat theme to %@"; +"Notification.YouDisabledTheme" = "You disabled chat theme"; + "Appstore.Cloud" = "**Cloud-based**\nUnlimited storage for chats,\nmedia and documents."; "Appstore.Cloud.Profile" = "**Jennifer**\n23 y.o. designer from San Francisco."; "Appstore.Creative" = "**Creative**\nColor themes, stickers, GIFs,\nvideo messages and more."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 832890b0a9..c510a9f3c0 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -290,9 +290,10 @@ public final class NavigateToChatControllerParams { public let options: NavigationAnimationOptions public let parentGroupId: PeerGroupId? public let chatListFilter: Int32? + public let changeColors: Bool public let completion: (ChatController) -> Void - public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, completion: @escaping (ChatController) -> Void = { _ in }) { + public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, changeColors: Bool = false, completion: @escaping (ChatController) -> Void = { _ in }) { self.navigationController = navigationController self.chatController = chatController self.chatLocationContextHolder = chatLocationContextHolder @@ -314,6 +315,7 @@ public final class NavigateToChatControllerParams { self.options = options self.parentGroupId = parentGroupId self.chatListFilter = chatListFilter + self.changeColors = changeColors self.completion = completion } } @@ -576,6 +578,7 @@ public protocol SharedAccountContext: AnyObject { var activeAccountsWithInfo: Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> { get } var presentGlobalController: (ViewController, Any?) -> Void { get } + var presentCrossfadeController: () -> Void { get } func makeTempAccountContext(account: Account) -> AccountContext @@ -587,7 +590,7 @@ public protocol SharedAccountContext: AnyObject { func openChatMessage(_ params: OpenChatMessageParams) -> Bool func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController - func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController? + func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController? func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController? func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController func makePeersNearbyController(context: AccountContext) -> ViewController diff --git a/submodules/AnimationUI/Sources/AnimationNode.swift b/submodules/AnimationUI/Sources/AnimationNode.swift index a034b855d5..5e99684673 100644 --- a/submodules/AnimationUI/Sources/AnimationNode.swift +++ b/submodules/AnimationUI/Sources/AnimationNode.swift @@ -44,6 +44,8 @@ public final class AnimationNode : ASDisplayNode { } } + view.logHierarchyKeypaths() + return view } else { return LOTAnimationView() diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 74c0ec4405..df5ed91372 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -208,7 +208,7 @@ public final class CallListController: TelegramBaseController { TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) |> deliverOnMainQueue).start(next: { peer in - if let strongSelf = self, let peer = peer, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer._asPeer(), mode: .calls(messages: messages.map({ $0._asMessage() })), avatarInitiallyExpanded: false, fromChat: false) { + if let strongSelf = self, let peer = peer, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .calls(messages: messages.map({ $0._asMessage() })), avatarInitiallyExpanded: false, fromChat: false) { (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) } }) diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index f635e0df83..7b83ff45d2 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -487,7 +487,7 @@ public class ContactsController: ViewController { } if let peer = peer { DispatchQueue.main.async { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) } } diff --git a/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift b/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift index 9a12c39c12..3ebc781735 100644 --- a/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift +++ b/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift @@ -115,6 +115,9 @@ public final class ListViewReorderingGestureRecognizer: UIGestureRecognizer { self.startLongPressTimer() } else { self.state = .began + if let itemNode = self.itemNode { + self.began(itemNode) + } } } else { self.state = .failed diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 11f2cf8a17..bd41443373 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -8,7 +8,7 @@ private var backArrowImageCache: [Int32: UIImage] = [:] public final class SparseNode: ASDisplayNode { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for view in self.view.subviews { - if let result = view.hitTest(self.view.convert(point, to: view), with: event) { + if let result = view.hitTest(self.view.convert(point, to: view), with: event), result.isUserInteractionEnabled { return result } } diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index a2ae752fe6..f8439221e5 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -1221,7 +1221,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { peer in if let strongSelf = self { - if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { strongSelf.getNavigationController()?.pushViewController(controller) } } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index 4ffd7525af..a2d2fb16ed 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -120,7 +120,7 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, media: AnyMed }) } -public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ((String) -> UIView?)?, @escaping () -> Void) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { +public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, updatedPresentationData: (initial: PresentationData, signal: Signal), parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ((String) -> UIView?)?, @escaping () -> Void) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { let defaultVideoPreset = defaultVideoPresetForContext(context) UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0") @@ -318,7 +318,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati legacyController.enableSizeClassSignal = true - let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak legacyController] presentationData in + let presentationDisposable = updatedPresentationData.signal.start(next: { [weak legacyController] presentationData in if let legacyController = legacyController, let controller = legacyController.legacyController as? TGMenuSheetController { controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme, forceDark: false) } diff --git a/submodules/PassportUI/Sources/SecureIdAuthController.swift b/submodules/PassportUI/Sources/SecureIdAuthController.swift index ec01a064a2..fb78437c94 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthController.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthController.swift @@ -329,7 +329,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable guard let strongSelf = self else { return } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) } }) diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift index 190afe015c..6553aa7e38 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift @@ -371,7 +371,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId) if let navigationController = getNavigationControllerImpl?() { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(participant.peer.id))) } - } else if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: participant.peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + } else if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: participant.peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { pushControllerImpl?(infoController) } })) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index d6f7991480..80e3223a69 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -423,7 +423,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) -> } })) }, openPeer: { peer in - if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { pushControllerImpl?(controller) } }, inviteViaLink: { @@ -483,7 +483,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) -> return state.withUpdatedSearchingMembers(false) } }, openPeer: { peer, _ in - if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { pushControllerImpl?(infoController) } }, pushController: { c in diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 5d9f4a6433..bd4c0927be 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -698,7 +698,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) }, openPeerInfo: { peer in - if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { pushControllerImpl?(controller) } }, openKicked: { diff --git a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift b/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift index 0a9d4c1e5d..13bb221089 100644 --- a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift +++ b/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift @@ -598,7 +598,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController { controller?.clearItemNodesHighlight(animated: true) } navigateToProfileImpl = { [weak controller] peer, distance in - if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false) { + if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false) { navigationController.pushViewController(controller) } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift index 5f9322d222..e180a40d92 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift @@ -261,7 +261,7 @@ public func blockedPeersController(context: AccountContext, blockedPeersContext: } })) }, openPeer: { peer in - if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { pushControllerImpl?(controller) } }) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index a3a4c368d7..9b76216883 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -319,7 +319,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri return transaction.getPeer(peerId) } |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) else { + guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) else { return } pushControllerImpl?(controller) diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index cf4652dc43..a48923e077 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -873,7 +873,7 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached let _ = (context.account.postbox.loadedPeerWithId(peerId) |> take(1) |> deliverOnMainQueue).start(next: { peer in - if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { navigationController.pushViewController(controller) } }) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index d72f1d2ef9..bd58cc6040 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -181,6 +181,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[608050278] = { return Api.SendMessageAction.parse_sendMessageUploadRoundAction($0) } dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) } dict[-606432698] = { return Api.SendMessageAction.parse_sendMessageHistoryImportAction($0) } + dict[-1336228175] = { return Api.SendMessageAction.parse_sendMessageChooseStickerAction($0) } dict[-1137792208] = { return Api.PrivacyKey.parse_privacyKeyStatusTimestamp($0) } dict[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($0) } dict[1030105979] = { return Api.PrivacyKey.parse_privacyKeyPhoneCall($0) } diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index 7bbea07397..e7082870e7 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -4281,6 +4281,7 @@ public extension Api { case sendMessageUploadRoundAction(progress: Int32) case speakingInGroupCallAction case sendMessageHistoryImportAction(progress: Int32) + case sendMessageChooseStickerAction public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -4373,6 +4374,12 @@ public extension Api { buffer.appendInt32(-606432698) } serializeInt32(progress, buffer: buffer, boxed: false) + break + case .sendMessageChooseStickerAction: + if boxed { + buffer.appendInt32(-1336228175) + } + break } } @@ -4409,6 +4416,8 @@ public extension Api { return ("speakingInGroupCallAction", []) case .sendMessageHistoryImportAction(let progress): return ("sendMessageHistoryImportAction", [("progress", progress)]) + case .sendMessageChooseStickerAction: + return ("sendMessageChooseStickerAction", []) } } @@ -4505,6 +4514,9 @@ public extension Api { return nil } } + public static func parse_sendMessageChooseStickerAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageChooseStickerAction + } } public enum PrivacyKey: TypeConstructorDescription { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index f8703a402f..4a46e49e1b 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -6160,17 +6160,6 @@ public final class VoiceChatController: ViewController { }), in: .window(.root)) } - private func displayToggleVideoSourceTooltip(screencast: Bool) { -// guard let videoContainerNode = self.mainStageVideoContainerNode else { -// return -// } -// -// let location = videoContainerNode.view.convert(videoContainerNode.otherVideoWrapperNode.frame, to: nil) -// self.controller?.present(TooltipScreen(text: screencast ? self.presentationData.strings.VoiceChat_TapToViewCameraVideo : self.presentationData.strings.VoiceChat_TapToViewScreenVideo, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in -// return .dismiss(consume: false) -// }), in: .window(.root)) - } - private var isScheduled: Bool { return self.isScheduling || self.callState?.scheduleTimestamp != nil } diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index ca0705fff2..aca987a8e3 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -235,6 +235,8 @@ private var declaredEncodables: Void = { declareEncodable(WallpapersState.self, f: { WallpapersState(decoder: $0) }) declareEncodable(WallpaperDataResource.self, f: { WallpaperDataResource(decoder: $0) }) declareEncodable(ForwardHideSendersNamesMessageAttribute.self, f: { ForwardHideSendersNamesMessageAttribute(decoder: $0) }) + declareEncodable(ChatTheme.self, f: { ChatTheme(decoder: $0) }) + declareEncodable(ChatThemes.self, f: { ChatThemes(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 7b99cffadb..3cd70e1c3f 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -77,8 +77,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe case let .inputGroupCall(id, accessHash): return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: scheduleDate, duration: nil)) } - case .messageActionSetChatTheme: - return nil + case let .messageActionSetChatTheme(emoji): + return TelegramMediaAction(action: .setChatTheme(emoji: emoji)) } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/Theme.swift b/submodules/TelegramCore/Sources/ApiUtils/Theme.swift index 9dfef6944f..b8c35e5709 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/Theme.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/Theme.swift @@ -5,7 +5,7 @@ import TelegramApi extension TelegramTheme { - convenience init?(apiTheme: Api.Theme) { + convenience init(apiTheme: Api.Theme) { switch apiTheme { case let .theme(flags, id, accessHash, slug, title, document, settings, installCount): self.init(id: id, accessHash: accessHash, slug: slug, title: title, file: document.flatMap(telegramMediaFileFromApiDocument), settings: settings.flatMap(TelegramThemeSettings.init(apiThemeSettings:)), isCreator: (flags & 1 << 0) != 0, isDefault: (flags & 1 << 1) != 0, installCount: installCount) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index b65823fdf5..95dfecbd39 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1412,9 +1412,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo } updatedState.deleteMessages(messageIds) case let .updateTheme(theme): - if let theme = TelegramTheme(apiTheme: theme) { - updatedState.updateTheme(theme) - } + updatedState.updateTheme(TelegramTheme(apiTheme: theme)) case let .updateMessageID(id, randomId): updatedState.updatedOutgoingUniqueMessageIds[randomId] = id case .updateDialogFilters: diff --git a/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift b/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift index 849115cec6..b0c73b6652 100644 --- a/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift +++ b/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift @@ -130,7 +130,7 @@ private func actionFromActivity(_ activity: PeerInputActivity?) -> Api.SendMessa case .speakingInGroupCall: return .speakingInGroupCallAction case .choosingSticker: - return .sendMessageGamePlayAction + return .sendMessageChooseStickerAction } } else { return .sendMessageCancelAction diff --git a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift index b6f0d316c0..862e2ee83b 100644 --- a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift +++ b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift @@ -66,6 +66,8 @@ extension PeerInputActivity { self = .uploadingInstantVideo(progress: progress) case .speakingInGroupCallAction: self = .speakingInGroupCall(timestamp: timestamp) + case .sendMessageChooseStickerAction: + self = .choosingSticker case let .sendMessageHistoryImportAction: return nil } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index 5b994dc253..8616c3980a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -224,6 +224,7 @@ public final class CachedChannelData: CachedPeerData { public let activeCall: ActiveCall? public let callJoinPeerId: PeerId? public let pendingSuggestions: [String] + public let themeEmoticon: String? public let peerIds: Set public let messageIds: Set @@ -257,9 +258,10 @@ public final class CachedChannelData: CachedPeerData { self.activeCall = nil self.callJoinPeerId = nil self.pendingSuggestions = [] + self.themeEmoticon = nil } - public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: LinkedDiscussionPeerId, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: ActiveCall?, callJoinPeerId: PeerId?, autoremoveTimeout: CachedPeerAutoremoveTimeout, pendingSuggestions: [String]) { + public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: LinkedDiscussionPeerId, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: ActiveCall?, callJoinPeerId: PeerId?, autoremoveTimeout: CachedPeerAutoremoveTimeout, pendingSuggestions: [String], themeEmoticon: String?) { self.isNotAccessible = isNotAccessible self.flags = flags self.about = about @@ -283,6 +285,7 @@ public final class CachedChannelData: CachedPeerData { self.callJoinPeerId = callJoinPeerId self.autoremoveTimeout = autoremoveTimeout self.pendingSuggestions = pendingSuggestions + self.themeEmoticon = themeEmoticon var peerIds = Set() for botInfo in botInfos { @@ -310,95 +313,99 @@ public final class CachedChannelData: CachedPeerData { } public func withUpdatedIsNotAccessible(_ isNotAccessible: Bool) -> CachedChannelData { - return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedFlags(_ flags: CachedChannelFlags) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedAbout(_ about: String?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedParticipantsSummary(_ participantsSummary: CachedChannelParticipantsSummary) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedStickerPack(_ stickerPack: StickerPackCollectionInfo?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedMinAvailableMessageId(_ minAvailableMessageId: MessageId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedMigrationReference(_ migrationReference: ChannelMigrationReference?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: LinkedDiscussionPeerId) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedSlowModeTimeout(_ slowModeTimeout: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedSlowModeValidUntilTimestamp(_ slowModeValidUntilTimestamp: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedStatsDatacenterId(_ statsDatacenterId: Int32) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedActiveCall(_ activeCall: ActiveCall?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedCallJoinPeerId(_ callJoinPeerId: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: autoremoveTimeout, pendingSuggestions: self.pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon) } public func withUpdatedPendingSuggestions(_ pendingSuggestions: [String]) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon) + } + + public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedChannelData { + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: themeEmoticon) } public init(decoder: PostboxDecoder) { @@ -475,6 +482,8 @@ public final class CachedChannelData: CachedPeerData { self.pendingSuggestions = decoder.decodeStringArrayForKey("sug") + self.themeEmoticon = decoder.decodeOptionalStringForKey("te") + if let photo = decoder.decodeObjectForKey("ph", decoder: { TelegramMediaImage(decoder: $0) }) as? TelegramMediaImage { self.photo = photo } else { @@ -600,6 +609,12 @@ public final class CachedChannelData: CachedPeerData { } encoder.encodeStringArray(self.pendingSuggestions, forKey: "sug") + + if let themeEmoticon = self.themeEmoticon, !themeEmoticon.isEmpty { + encoder.encodeString(themeEmoticon, forKey: "te") + } else { + encoder.encodeNil(forKey: "te") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -699,6 +714,10 @@ public final class CachedChannelData: CachedPeerData { return false } + if other.themeEmoticon != self.themeEmoticon { + return false + } + return true } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift index d19fbaafb9..073d5739c0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift @@ -51,14 +51,14 @@ public final class CachedGroupData: CachedPeerData { public let invitedBy: PeerId? public let photo: TelegramMediaImage? public let autoremoveTimeout: CachedPeerAutoremoveTimeout + public let activeCall: CachedChannelData.ActiveCall? + public let callJoinPeerId: PeerId? + public let themeEmoticon: String? public let peerIds: Set public let messageIds: Set public let associatedHistoryMessageId: MessageId? = nil - public let activeCall: CachedChannelData.ActiveCall? - public let callJoinPeerId: PeerId? - public init() { self.participants = nil self.exportedInvitation = nil @@ -73,12 +73,12 @@ public final class CachedGroupData: CachedPeerData { self.invitedBy = nil self.photo = nil self.autoremoveTimeout = .unknown - self.activeCall = nil self.callJoinPeerId = nil + self.themeEmoticon = nil } - public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags, hasScheduledMessages: Bool, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: CachedChannelData.ActiveCall?, autoremoveTimeout: CachedPeerAutoremoveTimeout, callJoinPeerId: PeerId?) { + public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags, hasScheduledMessages: Bool, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: CachedChannelData.ActiveCall?, autoremoveTimeout: CachedPeerAutoremoveTimeout, callJoinPeerId: PeerId?, themeEmoticon: String?) { self.participants = participants self.exportedInvitation = exportedInvitation self.botInfos = botInfos @@ -92,6 +92,7 @@ public final class CachedGroupData: CachedPeerData { self.activeCall = activeCall self.autoremoveTimeout = autoremoveTimeout self.callJoinPeerId = callJoinPeerId + self.themeEmoticon = themeEmoticon var messageIds = Set() if let pinnedMessageId = self.pinnedMessageId { @@ -152,6 +153,8 @@ public final class CachedGroupData: CachedPeerData { self.callJoinPeerId = decoder.decodeOptionalInt64ForKey("callJoinPeerId").flatMap(PeerId.init) + self.themeEmoticon = decoder.decodeOptionalStringForKey("te") + var messageIds = Set() if let pinnedMessageId = self.pinnedMessageId { messageIds.insert(pinnedMessageId) @@ -229,6 +232,12 @@ public final class CachedGroupData: CachedPeerData { } else { encoder.encodeNil(forKey: "callJoinPeerId") } + + if let themeEmoticon = self.themeEmoticon, !themeEmoticon.isEmpty { + encoder.encodeString(themeEmoticon, forKey: "te") + } else { + encoder.encodeNil(forKey: "te") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -244,58 +253,62 @@ public final class CachedGroupData: CachedPeerData { return false } - return self.participants == other.participants && self.exportedInvitation == other.exportedInvitation && self.botInfos == other.botInfos && self.peerStatusSettings == other.peerStatusSettings && self.pinnedMessageId == other.pinnedMessageId && self.about == other.about && self.flags == other.flags && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.invitedBy == other.invitedBy + return self.participants == other.participants && self.exportedInvitation == other.exportedInvitation && self.botInfos == other.botInfos && self.peerStatusSettings == other.peerStatusSettings && self.pinnedMessageId == other.pinnedMessageId && self.about == other.about && self.flags == other.flags && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.invitedBy == other.invitedBy && self.themeEmoticon == other.themeEmoticon } public func withUpdatedParticipants(_ participants: CachedGroupParticipants?) -> CachedGroupData { - return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedAbout(_ about: String?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedFlags(_ flags: CachedGroupFlags) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedActiveCall(_ activeCall: CachedChannelData.ActiveCall?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: autoremoveTimeout, callJoinPeerId: self.callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: autoremoveTimeout, callJoinPeerId: self.callJoinPeerId, themeEmoticon: self.themeEmoticon) } public func withUpdatedCallJoinPeerId(_ callJoinPeerId: PeerId?) -> CachedGroupData { - return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: callJoinPeerId) + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: callJoinPeerId, themeEmoticon: self.themeEmoticon) + } + + public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedGroupData { + return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout, callJoinPeerId: callJoinPeerId, themeEmoticon: themeEmoticon) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 9d186d2524..03abf8367d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -62,6 +62,7 @@ public final class CachedUserData: CachedPeerData { public let canPinMessages: Bool public let hasScheduledMessages: Bool public let autoremoveTimeout: CachedPeerAutoremoveTimeout + public let themeEmoticon: String? public let peerIds = Set() public let messageIds: Set @@ -80,10 +81,11 @@ public final class CachedUserData: CachedPeerData { self.canPinMessages = false self.hasScheduledMessages = false self.autoremoveTimeout = .unknown + self.themeEmoticon = nil self.messageIds = Set() } - public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout) { + public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?) { self.about = about self.botInfo = botInfo self.peerStatusSettings = peerStatusSettings @@ -96,6 +98,8 @@ public final class CachedUserData: CachedPeerData { self.canPinMessages = canPinMessages self.hasScheduledMessages = hasScheduledMessages self.autoremoveTimeout = autoremoveTimeout + self.themeEmoticon = themeEmoticon + var messageIds = Set() if let pinnedMessageId = self.pinnedMessageId { messageIds.insert(pinnedMessageId) @@ -126,6 +130,7 @@ public final class CachedUserData: CachedPeerData { self.canPinMessages = decoder.decodeInt32ForKey("cpm", orElse: 0) != 0 self.hasScheduledMessages = decoder.decodeBoolForKey("hsm", orElse: false) self.autoremoveTimeout = decoder.decodeObjectForKey("artv", decoder: CachedPeerAutoremoveTimeout.init(decoder:)) as? CachedPeerAutoremoveTimeout ?? .unknown + self.themeEmoticon = decoder.decodeOptionalStringForKey("te") var messageIds = Set() if let pinnedMessageId = self.pinnedMessageId { @@ -167,6 +172,11 @@ public final class CachedUserData: CachedPeerData { encoder.encodeInt32(self.canPinMessages ? 1 : 0, forKey: "cpm") encoder.encodeBool(self.hasScheduledMessages, forKey: "hsm") encoder.encodeObject(self.autoremoveTimeout, forKey: "artv") + if let themeEmoticon = self.themeEmoticon, !themeEmoticon.isEmpty { + encoder.encodeString(themeEmoticon, forKey: "te") + } else { + encoder.encodeNil(forKey: "te") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -181,54 +191,58 @@ public final class CachedUserData: CachedPeerData { return false } - return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout + return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon } public func withUpdatedAbout(_ about: String?) -> CachedUserData { - return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) } public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon) + } + + public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData { + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 61cde874e7..adeb35527a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -350,6 +350,7 @@ private enum SharedDataKeyValues: Int32 { case themeSettings = 6 case countriesList = 7 case wallapersState = 8 + case chatThemes = 9 } public struct SharedDataKeys { @@ -400,6 +401,12 @@ public struct SharedDataKeys { key.setInt32(0, value: SharedDataKeyValues.wallapersState.rawValue) return key }() + + public static let chatThemes: ValueBoxKey = { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: SharedDataKeyValues.chatThemes.rawValue) + return key + }() } public func applicationSpecificItemCacheCollectionId(_ value: Int8) -> Int8 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index ea805fcfe9..d9ebd093fe 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -48,6 +48,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case geoProximityReached(from: PeerId, to: PeerId, distance: Int32) case groupPhoneCall(callId: Int64, accessHash: Int64, scheduleDate: Int32?, duration: Int32?) case inviteToGroupPhoneCall(callId: Int64, accessHash: Int64, peerIds: [PeerId]) + case setChatTheme(emoji: String) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -110,6 +111,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { peerIds = decoder.decodeInt64ArrayForKey("peerIds").map(PeerId.init) } self = .inviteToGroupPhoneCall(callId: decoder.decodeInt64ForKey("callId", orElse: 0), accessHash: decoder.decodeInt64ForKey("accessHash", orElse: 0), peerIds: peerIds) + case 24: + self = .setChatTheme(emoji: decoder.decodeStringForKey("emoji", orElse: "")) default: self = .unknown } @@ -219,6 +222,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeInt64(callId, forKey: "callId") encoder.encodeInt64(accessHash, forKey: "accessHash") encoder.encodeInt64Array(peerIds.map { $0.toInt64() }, forKey: "peerIds") + case let .setChatTheme(emoji): + encoder.encodeInt32(24, forKey: "_rawValue") + encoder.encodeString(emoji, forKey: "emoji") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Localization/Countries.swift b/submodules/TelegramCore/Sources/TelegramEngine/Localization/Countries.swift index cc11b9ead1..143467c29c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Localization/Countries.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Localization/Countries.swift @@ -3,7 +3,6 @@ import Postbox import SwiftSignalKit import TelegramApi - public struct Country: PostboxCoding, Equatable { public static func == (lhs: Country, rhs: Country) -> Bool { return lhs.id == rhs.id && lhs.name == rhs.name && lhs.localizedName == rhs.localizedName && lhs.countryCodes == rhs.countryCodes && lhs.hidden == rhs.hidden diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 1d83865ccf..c53536bbd5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -210,6 +210,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee return previous.withUpdatedAbout(userFull.about).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFull.commonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages) .withUpdatedAutoremoveTimeout(autoremoveTimeout) + .withUpdatedThemeEmoticon(userFull.themeEmoticon) } }) return true @@ -320,6 +321,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedPhoto(photo) .withUpdatedActiveCall(updatedActiveCall) .withUpdatedCallJoinPeerId(groupCallDefaultJoinAs?.peerId) + .withUpdatedThemeEmoticon(chatFull.themeEmoticon) }) case .channelFull: break @@ -540,6 +542,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedCallJoinPeerId(groupcallDefaultJoinAs?.peerId) .withUpdatedAutoremoveTimeout(autoremoveTimeout) .withUpdatedPendingSuggestions(pendingSuggestions ?? []) + .withUpdatedThemeEmoticon(themeEmoticon) }) if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift b/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift index 4175d9e427..45302b053f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift @@ -39,6 +39,10 @@ public final class TelegramEngine { public lazy var localization: Localization = { return Localization(account: self.account) }() + + public lazy var themes: Themes = { + return Themes(account: self.account) + }() public lazy var messages: Messages = { return Messages(account: self.account) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift new file mode 100644 index 0000000000..a56f6e05a9 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -0,0 +1,153 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi + +public struct ChatTheme: PostboxCoding, Equatable { + public static func == (lhs: ChatTheme, rhs: ChatTheme) -> Bool { + return lhs.emoji == rhs.emoji && lhs.theme == rhs.theme && lhs.darkTheme == rhs.darkTheme + } + + public let emoji: String + public let theme: TelegramTheme + public let darkTheme: TelegramTheme + + public init(emoji: String, theme: TelegramTheme, darkTheme: TelegramTheme) { + self.emoji = emoji + self.theme = theme + self.darkTheme = darkTheme + } + + public init(decoder: PostboxDecoder) { + self.emoji = decoder.decodeStringForKey("e", orElse: "") + self.theme = decoder.decodeObjectForKey("t") as! TelegramTheme + self.darkTheme = decoder.decodeObjectForKey("dt") as! TelegramTheme + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.emoji, forKey: "e") + encoder.encodeObject(self.theme, forKey: "t") + encoder.encodeObject(self.darkTheme, forKey: "dt") + } +} + + +public final class ChatThemes: PreferencesEntry, Equatable { + public let chatThemes: [ChatTheme] + public let hash: Int32 + + public init(chatThemes: [ChatTheme], hash: Int32) { + self.chatThemes = chatThemes + self.hash = hash + } + + public init(decoder: PostboxDecoder) { + self.chatThemes = decoder.decodeObjectArrayForKey("c").map { $0 as! ChatTheme } + self.hash = decoder.decodeInt32ForKey("h", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectArray(self.chatThemes, forKey: "c") + encoder.encodeInt32(self.hash, forKey: "h") + } + + public func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? ChatThemes { + return self == to + } else { + return false + } + } + + public static func ==(lhs: ChatThemes, rhs: ChatThemes) -> Bool { + return lhs.chatThemes == rhs.chatThemes && lhs.hash == rhs.hash + } +} + +func _internal_getChatThemes(accountManager: AccountManager, network: Network, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[ChatTheme], NoError> { + let fetch: ([ChatTheme]?, Int32?) -> Signal<[ChatTheme], NoError> = { current, hash in + return network.request(Api.functions.account.getChatThemes(hash: hash ?? 0)) + |> retryRequest + |> mapToSignal { result -> Signal<[ChatTheme], NoError> in + switch result { + case let .chatThemes(hash, apiThemes): + let result = apiThemes.compactMap { ChatTheme(apiChatTheme: $0) } + if result == current { + return .complete() + } else { + let _ = accountManager.transaction { transaction in + transaction.updateSharedData(SharedDataKeys.chatThemes, { _ in + return ChatThemes(chatThemes: result, hash: hash) + }) + }.start() + return .single(result) + } + case .chatThemesNotModified: + return .complete() + } + } + } + + if forceUpdate { + return fetch(nil, nil) + } else { + return accountManager.sharedData(keys: [SharedDataKeys.chatThemes]) + |> take(1) + |> map { sharedData -> ([ChatTheme], Int32) in + if let chatThemes = sharedData.entries[SharedDataKeys.chatThemes] as? ChatThemes { + return (chatThemes.chatThemes, chatThemes.hash) + } else { + return ([], 0) + } + } + |> mapToSignal { current, hash -> Signal<[ChatTheme], NoError> in + if onlyCached { + return .single(current) + } else { + return .single(current) + |> then(fetch(current, hash)) + } + } + } +} + +func _internal_setChatTheme(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, emoji: String?) -> Signal { + return postbox.loadedPeerWithId(peerId) + |> mapToSignal { peer in + guard let inputPeer = apiInputPeer(peer) else { + return .complete() + } + + return postbox.transaction { transaction -> Signal in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedThemeEmoticon(emoji) + } else if let current = current as? CachedGroupData { + return current.withUpdatedThemeEmoticon(emoji) + } else if let current = current as? CachedChannelData { + return current.withUpdatedThemeEmoticon(emoji) + } else { + return current + } + }) + + return network.request(Api.functions.messages.setChatTheme(peer: inputPeer, emoticon: emoji ?? "")) + |> `catch` { error in + return .complete() + } + |> mapToSignal { updates -> Signal in + stateManager.addUpdates(updates) + return .complete() + } + } |> switchToLatest + } +} + +extension ChatTheme { + init(apiChatTheme: Api.ChatTheme) { + switch apiChatTheme { + case let .chatTheme(emoticon, theme, darkTheme): + self.init(emoji: emoticon, theme: TelegramTheme(apiTheme: theme), darkTheme: TelegramTheme(apiTheme: darkTheme)) + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift new file mode 100644 index 0000000000..4d38469472 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift @@ -0,0 +1,20 @@ +import SwiftSignalKit +import Postbox + +public extension TelegramEngine { + final class Themes { + private let account: Account + + init(account: Account) { + self.account = account + } + + public func getChatThemes(accountManager: AccountManager, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[ChatTheme], NoError> { + return _internal_getChatThemes(accountManager: accountManager, network: self.account.network, forceUpdate: forceUpdate, onlyCached: onlyCached) + } + + public func setChatTheme(peerId: PeerId, emoji: String?) -> Signal { + return _internal_setChatTheme(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, emoji: emoji) + } + } +} diff --git a/submodules/TelegramCore/Sources/Themes.swift b/submodules/TelegramCore/Sources/Themes.swift index e6250c214e..6a6a879f6d 100644 --- a/submodules/TelegramCore/Sources/Themes.swift +++ b/submodules/TelegramCore/Sources/Themes.swift @@ -3,7 +3,6 @@ import Postbox import SwiftSignalKit import TelegramApi - #if os(macOS) let telegramThemeFormat = "macos" let telegramThemeFileExtension = "palette" @@ -101,11 +100,7 @@ public func getTheme(account: Account, slug: String) -> Signal mapToSignal { theme -> Signal in - if let theme = TelegramTheme(apiTheme: theme) { - return .single(theme) - } else { - return .fail(.generic) - } + return .single(TelegramTheme(apiTheme: theme)) } } @@ -122,11 +117,7 @@ private func checkThemeUpdated(network: Network, theme: TelegramTheme) -> Signal return network.request(Api.functions.account.getTheme(format: telegramThemeFormat, theme: .inputTheme(id: theme.id, accessHash: theme.accessHash), documentId: documentId)) |> mapError { _ -> GetThemeError in return .generic } |> map { theme -> ThemeUpdatedResult in - if let theme = TelegramTheme(apiTheme: theme) { - return .updated(theme) - } else { - return .notModified - } + return .updated(TelegramTheme(apiTheme: theme)) } } @@ -310,24 +301,21 @@ public func createTheme(account: Account, title: String, resource: MediaResource return .generic } |> mapToSignal { apiTheme -> Signal in - if let theme = TelegramTheme(apiTheme: apiTheme) { - return account.postbox.transaction { transaction -> CreateThemeResult in - let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) - var items = entries.map { $0.contents as! TelegramTheme } - items.insert(theme, at: 0) - 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) - return .result(theme) + let theme = TelegramTheme(apiTheme: apiTheme) + return account.postbox.transaction { transaction -> CreateThemeResult in + let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) + var items = entries.map { $0.contents as! TelegramTheme } + items.insert(theme, at: 0) + 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)) } - |> castError(CreateThemeError.self) - } else { - return .fail(.generic) + transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) + return .result(theme) } + |> castError(CreateThemeError.self) } } else { @@ -348,26 +336,23 @@ public func createTheme(account: Account, title: String, resource: MediaResource return .generic } |> mapToSignal { apiTheme -> Signal in - if var theme = TelegramTheme(apiTheme: apiTheme) { - theme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount) + var theme = TelegramTheme(apiTheme: apiTheme) + theme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount) - return account.postbox.transaction { transaction -> CreateThemeResult in - let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) - var items = entries.map { $0.contents as! TelegramTheme } - items.insert(theme, at: 0) - 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) - return .result(theme) - } - |> castError(CreateThemeError.self) - } else { - return .fail(.generic) + return account.postbox.transaction { transaction -> CreateThemeResult in + let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) + var items = entries.map { $0.contents as! TelegramTheme } + items.insert(theme, at: 0) + 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) + return .result(theme) } + |> castError(CreateThemeError.self) } } } @@ -429,41 +414,38 @@ public func updateTheme(account: Account, accountManager: AccountManager mapToSignal { apiTheme -> Signal in - if let theme = TelegramTheme(apiTheme: apiTheme) { - let updatedTheme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount) + let theme = TelegramTheme(apiTheme: apiTheme) + let updatedTheme = TelegramTheme(id: theme.id, accessHash: theme.accessHash, slug: theme.slug, title: theme.title, file: theme.file, settings: settings, isCreator: theme.isCreator, isDefault: theme.isDefault, installCount: theme.installCount) - 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 == updatedTheme.id { - return updatedTheme - } else { - return theme - } + 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) } - 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)) + 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 == updatedTheme.id { + return updatedTheme + } else { + return theme } - transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudThemes, items: updatedEntries) - return .result(updatedTheme) } - |> castError(CreateThemeError.self) - } else { - return .fail(.generic) + 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) + return .result(updatedTheme) } + |> castError(CreateThemeError.self) } } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index b50bf8fc17..413a7daeac 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -752,7 +752,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio dimColor: UIColor(white: 0.0, alpha: 0.4), backgroundType: .light, opaqueItemBackgroundColor: UIColor(rgb: 0xffffff), - itemBackgroundColor: UIColor(white: 1.0, alpha: 0.87), + itemBackgroundColor: UIColor(white: 1.0, alpha: 0.8), opaqueItemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 1.0), itemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 0.7), opaqueItemSeparatorColor: UIColor(white: 0.9, alpha: 1.0), diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index 18a9767964..a54199e3d7 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -108,6 +108,10 @@ public final class PresentationData: Equatable { return PresentationData(strings: self.strings, theme: theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji) } + public func withUpdated(chatWallpaper: TelegramWallpaper) -> PresentationData { + return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji) + } + public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool { return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.autoNightModeTriggered == rhs.autoNightModeTriggered && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatFontSize == rhs.chatFontSize && lhs.chatBubbleCorners == rhs.chatBubbleCorners && lhs.listsFontSize == rhs.listsFontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.reduceMotion == rhs.reduceMotion && lhs.largeEmoji == rhs.largeEmoji } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 03148bdba2..794d0b1a53 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -528,6 +528,24 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) + case let .setChatTheme(emoji): + if emoji.isEmpty { + if message.author?.id == accountPeerId { + attributedString = NSAttributedString(string: strings.Notification_YouDisabledTheme, font: titleFont, textColor: primaryTextColor) + } else { + let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)] + let resultTitleString = strings.Notification_DisabledTheme(authorName) + attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) + } + } else { + if message.author?.id == accountPeerId { + attributedString = NSAttributedString(string: strings.Notification_YouChangedTheme(emoji).string, font: titleFont, textColor: primaryTextColor) + } else { + let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)] + let resultTitleString = strings.Notification_ChangedTheme(authorName, emoji) + attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) + } + } case .unknown: attributedString = nil } diff --git a/submodules/TelegramUI/Resources/Animations/anim_sun.json b/submodules/TelegramUI/Resources/Animations/anim_sun.json new file mode 100644 index 0000000000..48a7c6e8f3 --- /dev/null +++ b/submodules/TelegramUI/Resources/Animations/anim_sun.json @@ -0,0 +1 @@ +{"v":"5.6.5","fr":60,"ip":0,"op":36,"w":100,"h":100,"nm":"Sun Lottie 2","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Path 25","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[68,102.75,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,-100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Path 24","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33.625,68.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Path 43","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-15,"ix":10},"p":{"a":0,"k":[44.303,92.581,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":120,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Path 41","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[92.437,92.956,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":120,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Path 18","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[92.375,44.125,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,-100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":150,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Path 39","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-15,"ix":10},"p":{"a":0,"k":[43.688,44.25,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[-100,-100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":150,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Path 15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[100.625,68.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Path 14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[68,34.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":6,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Sunny","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[0]},{"t":21,"s":[100]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.316],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":12,"s":[-50]},{"t":36,"s":[0]}],"ix":10},"p":{"a":0,"k":[50,50,0],"ix":2},"a":{"a":0,"k":[68,68,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.38,0.38,0.833],"y":[1,1,1]},"o":{"x":[0.215,0.215,0.167],"y":[0,0,0]},"t":12,"s":[110,110,100]},{"t":23,"s":[100,100,100]}],"ix":6}},"ao":0,"w":136,"h":136,"ip":14,"op":36,"st":13.5,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Path","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":0,"s":[0]},{"t":20,"s":[80]}],"ix":10},"p":{"a":0,"k":[53.448,53.382,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.333],"y":[0,0,0]},"t":1,"s":[76.666,76.666,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":18,"s":[71,71,100]},{"t":35,"s":[76,76,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.735,"y":1},"o":{"x":0.857,"y":0},"t":0,"s":[{"i":[[2.91,2.05],[0,-0.35],[4.26,-3.65],[1.74,0.68],[-0.19,-0.35],[-4.28,0],[0,5.33]],"o":[[-0.41,-0.29],[0,1.49],[-4.26,3.65],[-0.46,-0.18],[1.93,3.75],[5.2,0],[0,-3.49]],"v":[[4.526,-9.188],[3.736,-8.738],[0.696,1.182],[-9.534,2.962],[-10.154,3.662],[-0.854,9.292],[9.036,-0.678]],"c":true}]},{"t":18,"s":[{"i":[[1.88,1.32],[0.45,0.17],[2.33,-2.14],[-0.21,-0.94],[-0.12,-0.23],[-2.59,0],[0,3.44]],"o":[[-0.26,-0.19],[-1.68,-0.65],[-2.32,2.14],[0.09,0.44],[1.25,2.42],[3.36,0],[0,-1.93]],"v":[[2.586,-4.585],[1.186,-5.325],[-5.654,-3.965],[-7.484,2.185],[-6.974,3.615],[-0.964,7.245],[5.356,0.755]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.25,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[447.223,436.847],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Path 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.875,40.625,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.84,0.84,0.667],"y":[1,1,1]},"o":{"x":[0.607,0.607,0.333],"y":[0,0,0]},"t":1,"s":[100,100,100]},{"i":{"x":[0.514,0.514,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":6,"s":[120,120,100]},{"t":13,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-0.08],[0.08,0],[0,-0.67],[0.08,0],[0,0.08],[0.67,0],[0,0.08],[-0.08,0],[0,0.67],[-0.07,0],[0,-0.08],[-0.67,0]],"o":[[0,0.08],[-0.67,0],[0,0.08],[-0.07,0],[0,-0.67],[-0.08,0],[0,-0.08],[0.67,0],[0,-0.08],[0.08,0],[0,0.67],[0.08,0]],"v":[[1.5,0],[1.36,0.14],[0.14,1.36],[0,1.5],[-0.14,1.36],[-1.36,0.14],[-1.5,0],[-1.36,-0.14],[-0.14,-1.36],[0,-1.5],[0.14,-1.36],[1.36,-0.14]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":-2,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Path 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.875,28.625,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.84,0.84,0.667],"y":[1,1,1]},"o":{"x":[0.607,0.607,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.5,0.5,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":5,"s":[120,120,100]},{"t":12,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-0.1],[0.1,0],[0,-0.9],[0.1,0],[0,0.1],[0.9,0],[0,0.1],[-0.1,0],[0,0.9],[-0.1,0],[0,-0.1],[-0.9,0]],"o":[[0,0.1],[-0.9,0],[0,0.1],[-0.1,0],[0,-0.9],[-0.1,0],[0,-0.1],[0.9,0],[0,-0.1],[0.1,0],[0,0.9],[0.1,0]],"v":[[2,0],[1.82,0.18],[0.18,1.82],[0,2],[-0.18,1.82],[-1.82,0.18],[-2,0],[-1.82,-0.18],[-0.18,-1.82],[0,-2],[0.18,-1.82],[1.82,-0.18]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":-2,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/anim_sun_reverse.json b/submodules/TelegramUI/Resources/Animations/anim_sun_reverse.json new file mode 100644 index 0000000000..73a856b0f4 --- /dev/null +++ b/submodules/TelegramUI/Resources/Animations/anim_sun_reverse.json @@ -0,0 +1 @@ +{"v":"5.6.5","fr":60,"ip":0,"op":41,"w":100,"h":100,"nm":"Sun Lottie 3","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Path 25","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[68,102.75,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,-100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Path 24","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33.625,68.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Path 43","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-15,"ix":10},"p":{"a":0,"k":[44.303,92.581,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":120,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Path 41","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[92.437,92.956,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":120,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Path 18","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[92.375,44.125,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,-100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":150,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Path 39","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-15,"ix":10},"p":{"a":0,"k":[43.688,44.25,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[-100,-100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":150,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Path 15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[100.625,68.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":13,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Path 14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[68,34.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,2.5],[0,-2.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[30]},{"t":54,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[18]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":54,"s":[1]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[120]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[172]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[120]},{"t":54,"s":[0]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-5,"op":89,"st":6,"bm":0}]}],"layers":[{"ddd":0,"ind":2,"ty":0,"nm":"Sunny","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[100]},{"t":24,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.69],"y":[0.936]},"o":{"x":[0.604],"y":[0.208]},"t":-2,"s":[0]},{"t":22,"s":[-50]}],"ix":10},"p":{"a":0,"k":[50,50,0],"ix":2},"a":{"a":0,"k":[68,68,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.46,0.46,0.167],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"t":22,"s":[110,110,100]}],"ix":6}},"ao":0,"w":136,"h":136,"ip":0,"op":36,"st":-30.5,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Path","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.529],"y":[0.041]},"t":20,"s":[80]},{"t":40,"s":[0]}],"ix":10},"p":{"a":0,"k":[53.448,53.382,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":21,"s":[71,71,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.333],"y":[0,0,0]},"t":38,"s":[76.666,76.666,100]},{"t":55,"s":[76,76,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.386,"y":1},"o":{"x":0.356,"y":0},"t":20,"s":[{"i":[[1.88,1.32],[0.45,0.17],[2.33,-2.14],[-0.21,-0.94],[-0.12,-0.23],[-2.59,0],[0,3.44]],"o":[[-0.26,-0.19],[-1.68,-0.65],[-2.32,2.14],[0.09,0.44],[1.25,2.42],[3.36,0],[0,-1.93]],"v":[[2.549,-4.49],[1.149,-5.23],[-5.691,-3.87],[-7.521,2.28],[-7.011,3.71],[-1.001,7.34],[5.319,0.85]],"c":true}]},{"t":38,"s":[{"i":[[2.91,2.05],[0,-0.35],[4.26,-3.65],[1.74,0.68],[-0.19,-0.35],[-4.28,0],[0,5.33]],"o":[[-0.41,-0.29],[0,1.49],[-4.26,3.65],[-0.46,-0.18],[1.93,3.75],[5.2,0],[0,-3.49]],"v":[[4.526,-9.188],[3.736,-8.738],[0.696,1.182],[-9.534,2.962],[-10.154,3.662],[-0.854,9.292],[9.036,-0.678]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.25,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[447.223,436.847],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":56,"st":14,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Path 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.875,40.625,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":28,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.607,0.607,0.333],"y":[0,0,0]},"t":34,"s":[100,100,100]},{"t":41,"s":[120,120,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-0.08],[0.08,0],[0,-0.67],[0.08,0],[0,0.08],[0.67,0],[0,0.08],[-0.08,0],[0,0.67],[-0.07,0],[0,-0.08],[-0.67,0]],"o":[[0,0.08],[-0.67,0],[0,0.08],[-0.07,0],[0,-0.67],[-0.08,0],[0,-0.08],[0.67,0],[0,-0.08],[0.08,0],[0,0.67],[0.08,0]],"v":[[1.5,0],[1.36,0.14],[0.14,1.36],[0,1.5],[-0.14,1.36],[-1.36,0.14],[-1.5,0],[-1.36,-0.14],[-0.14,-1.36],[0,-1.5],[0.14,-1.36],[1.36,-0.14]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":28,"op":64,"st":26,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Path 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.875,28.625,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.84,0.84,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":28,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":33,"s":[120,120,100]},{"t":41,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-0.1],[0.1,0],[0,-0.9],[0.1,0],[0,0.1],[0.9,0],[0,0.1],[-0.1,0],[0,0.9],[-0.1,0],[0,-0.1],[-0.9,0]],"o":[[0,0.1],[-0.9,0],[0,0.1],[-0.1,0],[0,-0.9],[-0.1,0],[0,-0.1],[0.9,0],[0,-0.1],[0.1,0],[0,0.9],[0.1,0]],"v":[[2,0],[1.82,0.18],[0.18,1.82],[0,2],[-0.18,1.82],[-1.82,0.18],[-2,0],[-1.82,-0.18],[-0.18,-1.82],[0,-2],[0.18,-1.82],[1.82,-0.18]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[400,400],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":28,"op":64,"st":26,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 98d733e38f..e9facb3e5a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -243,6 +243,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var presentationInterfaceState: ChatPresentationInterfaceState private var presentationInterfaceStatePromise: ValuePromise + private var cachedDataPromise = Promise(nil) + private var chatTitleView: ChatTitleView? private var leftNavigationButton: ChatNavigationButton? private var rightNavigationButton: ChatNavigationButton? @@ -345,8 +347,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var canReadHistoryValue = false private var canReadHistoryDisposable: Disposable? - private var presentationData: PresentationData + private var themeEmoticonPreviewPromise = ValuePromise(nil) + private var themeDarkAppearancePreviewPromise = ValuePromise(nil) + private var presentationDataPromise = Promise() + private var presentationData: PresentationData { + didSet { + self.presentationDataPromise.set(.single(self.presentationData)) + } + } private var presentationDataDisposable: Disposable? + var updatedPresentationData: (PresentationData, Signal) { + return (self.presentationData, self.presentationDataPromise.get()) + } private var automaticMediaDownloadSettings: MediaAutoDownloadSettings private var automaticMediaDownloadSettingsDisposable: Disposable? @@ -700,6 +712,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }*/ return true + case .setChatTheme: + strongSelf.presentThemeSelection() + return true default: break } @@ -2835,7 +2850,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatTitleView?.pressed = { [weak self] in if let strongSelf = self { if strongSelf.chatLocation == .peer(strongSelf.context.account.peerId) { - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true) { + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } } else { @@ -3740,13 +3755,48 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - self.presentationDataDisposable = (context.sharedContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in + let themeEmoticon: Signal = self.cachedDataPromise.get() + |> map { cachedData -> String? in + if let cachedData = cachedData as? CachedUserData { + return cachedData.themeEmoticon + } else if let cachedData = cachedData as? CachedUserData { + return cachedData.themeEmoticon + } else if let cachedData = cachedData as? CachedUserData { + return cachedData.themeEmoticon + } else { + return nil + } + } + |> distinctUntilChanged + + self.presentationDataDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, context.engine.themes.getChatThemes(accountManager: context.sharedContext.accountManager, onlyCached: true), themeEmoticon, self.themeEmoticonPreviewPromise.get(), self.themeDarkAppearancePreviewPromise.get()).start(next: { [weak self] presentationData, chatThemes, themeEmoticon, themeEmoticonPreview, darkAppearancePreview in if let strongSelf = self { let previousTheme = strongSelf.presentationData.theme let previousStrings = strongSelf.presentationData.strings let previousChatWallpaper = strongSelf.presentationData.chatWallpaper + var themeEmoticon = themeEmoticon + if let themeEmoticonPreview = themeEmoticonPreview { + if !themeEmoticonPreview.isEmpty { + themeEmoticon = themeEmoticonPreview + } else { + themeEmoticon = nil + } + } + + var isDarkAppearance = presentationData.theme.overallDarkAppearance + if let darkAppearancePreview = darkAppearancePreview { + isDarkAppearance = darkAppearancePreview + } + + var presentationData = presentationData + if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) { + let customTheme = isDarkAppearance ? theme.darkTheme : theme.theme + if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) { + presentationData = presentationData.withUpdated(theme: theme) + presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper) + } + } strongSelf.presentationData = presentationData if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper { @@ -4471,6 +4521,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { let (cachedData, messages) = cachedDataAndMessages + strongSelf.cachedDataPromise.set(.single(cachedData)) + var pinnedMessageId: MessageId? var peerIsBlocked: Bool = false var callsAvailable: Bool = false @@ -8425,7 +8477,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> deliverOnMainQueue).start(next: { [weak self] peerView in if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible { if peer.id == strongSelf.context.account.peerId { - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true) { + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } } else { @@ -8436,7 +8488,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { expandAvatar = false } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar, fromChat: true) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar, fromChat: true) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } } @@ -8799,7 +8851,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G slowModeEnabled = true } - let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, presentationData: strongSelf.presentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText.string, openGallery: { + let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, presentationData: strongSelf.presentationData, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText.string, openGallery: { self?.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting, scheduleTime in if !inputText.string.isEmpty { //strongSelf.clearInputText() @@ -11322,7 +11374,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { expandAvatar = false } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } } @@ -11775,7 +11827,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } } @@ -12834,6 +12886,73 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.push(controller) } + private func presentCrossfadeSnapshot() { + guard let snapshotView = self.view.snapshotView(afterScreenUpdates: false) else { + return + } + self.view.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + + public func presentThemeSelection() { + let context = self.context + let peerId = self.chatLocation.peerId + + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + var updated = state + updated = updated.updatedInputMode({ _ in + return .none + }) + updated = updated.updatedShowCommands(false) + return updated + }) + + let _ = (self.cachedDataPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] cachedData in + guard let strongSelf = self else { + return + } + + let selectedEmoticon: String? + if let cachedData = cachedData as? CachedUserData { + selectedEmoticon = cachedData.themeEmoticon + } else if let cachedData = cachedData as? CachedGroupData { + selectedEmoticon = cachedData.themeEmoticon + } else if let cachedData = cachedData as? CachedChannelData { + selectedEmoticon = cachedData.themeEmoticon + } else { + selectedEmoticon = nil + } + + let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, initiallySelectedEmoticon: selectedEmoticon, previewTheme: { [weak self] emoticon in + if let strongSelf = self { + strongSelf.presentCrossfadeSnapshot() + strongSelf.themeEmoticonPreviewPromise.set(emoticon) + if emoticon == nil { + strongSelf.themeDarkAppearancePreviewPromise.set(nil) + } + } + }, previewDarkTheme: { dark in + if let strongSelf = self { + strongSelf.presentCrossfadeSnapshot() + strongSelf.themeDarkAppearancePreviewPromise.set(dark) + } + }, completion: { [weak self] emoticon in + strongSelf.themeDarkAppearancePreviewPromise.set(nil) + let _ = context.engine.themes.setChatTheme(peerId: peerId, emoji: emoticon).start(completed: { [weak self] in + if let strongSelf = self { + strongSelf.themeEmoticonPreviewPromise.set(nil) + } + }) + }) + strongSelf.present(controller, in: .window(.root)) + }) + } + private var effectiveNavigationController: NavigationController? { if let navigationController = self.navigationController as? NavigationController { return navigationController diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index ecaa100d9c..8ec57a48bd 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -247,7 +247,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inputContextPanelContainer = ChatControllerTitlePanelNodeContainer() var getMessageTransitionNode: (() -> ChatMessageTransitionNode?)? - self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), messageTransitionNode: { + self.historyNode = ChatHistoryListNode(context: context, updatedPresentationData: controller?.updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), messageTransitionNode: { return getMessageTransitionNode?() }) self.historyNode.rotated = true diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index ebe649f8f1..3cc3a8dde2 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -559,7 +559,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let clientId: Atomic - public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?, source: ChatHistoryListSource = .default, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNode? = { nil }) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?, source: ChatHistoryListSource = .default, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNode? = { nil }) { var tagMask = tagMask var appendMessagesFromTheSameGroup = false if case .pinnedMessages = subject { @@ -575,7 +575,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.controllerInteraction = controllerInteraction self.mode = mode - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let presentationData = updatedPresentationData.initial self.currentPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners, animatedEmojiScale: 1.0) self.chatPresentationDataPromise = Promise(self.currentPresentationData) @@ -1088,7 +1088,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.presentationDataDisposable = ( combineLatest(queue: .mainQueue(), - context.sharedContext.presentationData, + updatedPresentationData.signal, appConfiguration) |> deliverOnMainQueue).start(next: { [weak self] presentationData, appConfiguration in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift index ce75a0619e..1d6d49925d 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift @@ -225,13 +225,15 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true, nilIfEmpty: true)) let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false - self.imageNode.isHidden = loopAnimatedStickers - + let animatedStickerNode: AnimatedStickerNode if let current = self.animatedStickerNode { animatedStickerNode = current } else { animatedStickerNode = AnimatedStickerNode() + animatedStickerNode.started = { [weak self] in + self?.imageNode.isHidden = true + } self.animatedStickerNode = animatedStickerNode if let placeholderNode = self.placeholderNode { self.scalingNode.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode) diff --git a/submodules/TelegramUI/Sources/ChatMessageBackground.swift b/submodules/TelegramUI/Sources/ChatMessageBackground.swift index c618c7d7b9..6a82be5f98 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBackground.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBackground.swift @@ -228,11 +228,11 @@ class ChatMessageBackground: ASDisplayNode { }) } } else if transition.isAnimated { - if let previousContents = self.imageNode.layer.contents, let image = image { - if (previousContents as AnyObject) !== image.cgImage { - self.imageNode.layer.animate(from: previousContents as AnyObject, to: image.cgImage! as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.42) - } - } +// if let previousContents = self.imageNode.layer.contents, let image = image { +// if (previousContents as AnyObject) !== image.cgImage { +// self.imageNode.layer.animate(from: previousContents as AnyObject, to: image.cgImage! as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.42) +// } +// } } self.imageNode.image = image diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 4b233555f4..38e2f92cd0 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -772,7 +772,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), peekData: peekData, animated: true)) } else { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { strongSelf.pushController(infoController) } } @@ -786,7 +786,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self { if let peer = peer { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { strongSelf.pushController(infoController) } } diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift new file mode 100644 index 0000000000..9f4ecc1a38 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -0,0 +1,918 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import SolidRoundedButtonNode +import TelegramPresentationData +import TelegramUIPreferences +import PresentationDataUtils +import AnimationUI +import MergeLists +import WallpaperResources +import TooltipUI + +private func closeButtonImage(theme: PresentationTheme) -> UIImage? { + return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(theme.actionSheet.inputBackgroundColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(theme.actionSheet.inputClearButtonColor.cgColor) + + context.move(to: CGPoint(x: 10.0, y: 10.0)) + context.addLine(to: CGPoint(x: 20.0, y: 20.0)) + context.strokePath() + + context.move(to: CGPoint(x: 20.0, y: 10.0)) + context.addLine(to: CGPoint(x: 10.0, y: 20.0)) + context.strokePath() + }) +} + +private struct ThemeSettingsThemeEntry: Comparable, Identifiable { + let index: Int + let emoticon: String? + let themeReference: PresentationThemeReference? + var selected: Bool + let theme: PresentationTheme + let wallpaper: TelegramWallpaper? + + var stableId: Int { + return index + } + + static func ==(lhs: ThemeSettingsThemeEntry, rhs: ThemeSettingsThemeEntry) -> Bool { + if lhs.index != rhs.index { + return false + } + if lhs.emoticon != rhs.emoticon { + return false + } + if lhs.themeReference?.index != rhs.themeReference?.index { + return false + } + if lhs.selected != rhs.selected { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.wallpaper != rhs.wallpaper { + return false + } + return true + } + + static func <(lhs: ThemeSettingsThemeEntry, rhs: ThemeSettingsThemeEntry) -> Bool { + return lhs.index < rhs.index + } + + func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem { + return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, themeReference: self.themeReference, selected: self.selected, theme: self.theme, wallpaper: self.wallpaper, action: action) + } +} + + +private class ThemeSettingsThemeIconItem: ListViewItem { + let context: AccountContext + let emoticon: String? + let themeReference: PresentationThemeReference? + let selected: Bool + let theme: PresentationTheme + let wallpaper: TelegramWallpaper? + let action: (String?) -> Void + + public init(context: AccountContext, emoticon: String?, themeReference: PresentationThemeReference?, selected: Bool, theme: PresentationTheme, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) { + self.context = context + self.emoticon = emoticon + self.themeReference = themeReference + self.selected = selected + self.theme = theme + self.wallpaper = wallpaper + self.action = action + } + + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ThemeSettingsThemeItemIconNode() + let (nodeLayout, apply) = node.asyncLayout()(self, params) + node.insets = nodeLayout.insets + node.contentSize = nodeLayout.contentSize + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in + apply(false) + }) + }) + } + } + } + + public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + assert(node() is ThemeSettingsThemeItemIconNode) + if let nodeValue = node() as? ThemeSettingsThemeItemIconNode { + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params) + Queue.mainQueue().async { + completion(nodeLayout, { _ in + apply(animation.isAnimated) + }) + } + } + } + } + } + + public var selectable = true + public func selected(listView: ListView) { + self.action(self.emoticon) + } +} + +private struct ThemeSettingsThemeItemNodeTransition { + let deletions: [ListViewDeleteItem] + let insertions: [ListViewInsertItem] + let updates: [ListViewUpdateItem] + let crossfade: Bool + let entries: [ThemeSettingsThemeEntry] +} + +private func ensureThemeVisible(listNode: ListView, emoticon: String?, animated: Bool) -> Bool { + var resultNode: ThemeSettingsThemeItemIconNode? + listNode.forEachItemNode { node in + if resultNode == nil, let node = node as? ThemeSettingsThemeItemIconNode { + if node.item?.emoticon == emoticon { + resultNode = node + } + } + } + if let resultNode = resultNode { + listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 57.0) + return true + } else { + return false + } +} + +private func preparedTransition(context: AccountContext, action: @escaping (String?) -> Void, from fromEntries: [ThemeSettingsThemeEntry], to toEntries: [ThemeSettingsThemeEntry], crossfade: Bool) -> ThemeSettingsThemeItemNodeTransition { + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) + + let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action), directionHint: .Down) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action), directionHint: nil) } + + return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade, entries: toEntries) +} + +private var cachedBorderImages: [String: UIImage] = [:] +private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? { + let key = "\(theme.list.itemBlocksBackgroundColor.hexString)_\(selected ? "s" + theme.list.itemAccentColor.hexString : theme.list.disclosureArrowColor.hexString)" + if let image = cachedBorderImages[key] { + return image + } else { + let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let lineWidth: CGFloat + if selected { + lineWidth = 2.0 + context.setLineWidth(lineWidth) + context.setStrokeColor(theme.list.itemBlocksBackgroundColor.cgColor) + + context.strokeEllipse(in: bounds.insetBy(dx: 3.0 + lineWidth / 2.0, dy: 3.0 + lineWidth / 2.0)) + + var accentColor = theme.list.itemAccentColor + if accentColor.rgb == 0xffffff { + accentColor = UIColor(rgb: 0x999999) + } + context.setStrokeColor(accentColor.cgColor) + } 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: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0)) + } + })?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9) + cachedBorderImages[key] = image + return image + } +} + +private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { + private let containerNode: ASDisplayNode + private let imageNode: TransformImageNode + private let overlayNode: ASImageNode + private let textNode: TextNode + private let emojiNode: TextNode + var snapshotView: UIView? + + var item: ThemeSettingsThemeIconItem? + + init() { + self.containerNode = ASDisplayNode() + + self.imageNode = TransformImageNode() + self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 82.0, height: 108.0)) + self.imageNode.isLayerBacked = true + + self.overlayNode = ASImageNode() + self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 84.0, height: 110.0)) + self.overlayNode.isLayerBacked = true + + self.textNode = TextNode() + self.textNode.isUserInteractionEnabled = false + + self.emojiNode = TextNode() + self.emojiNode.isUserInteractionEnabled = false + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.imageNode) + self.containerNode.addSubnode(self.overlayNode) + self.containerNode.addSubnode(self.textNode) + self.containerNode.addSubnode(self.emojiNode) + } + + func asyncLayout() -> (ThemeSettingsThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { + let makeTextLayout = TextNode.asyncLayout(self.textNode) + let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode) + let makeImageLayout = self.imageNode.asyncLayout() + + let currentItem = self.item + + return { [weak self] item, params in + var updatedThemeReference = false + var updatedTheme = false + var updatedWallpaper = false + var updatedSelected = false + + if currentItem?.themeReference != item.themeReference { + updatedThemeReference = true + } + if currentItem?.wallpaper != item.wallpaper { + updatedWallpaper = true + } + if currentItem?.theme !== item.theme { + updatedTheme = true + } + if currentItem?.selected != item.selected { + updatedSelected = true + } + + let text = NSAttributedString(string: "No\nTheme", font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor) + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let title = NSAttributedString(string: item.emoticon ?? "❌", font: Font.regular(22.0), textColor: .black) + let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 120.0, height: 90.0), insets: UIEdgeInsets()) + return (itemLayout, { animated in + if let strongSelf = self { + strongSelf.item = item + + if updatedThemeReference || updatedWallpaper { + if let themeReference = item.themeReference { + strongSelf.imageNode.cornerRadius = 0.0 + strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, emoticon: true)) + strongSelf.imageNode.backgroundColor = nil + } + } + if item.themeReference == nil { + strongSelf.imageNode.cornerRadius = 9.0 + strongSelf.imageNode.backgroundColor = item.theme.actionSheet.opaqueItemBackgroundColor + } + + if updatedTheme || updatedSelected { + strongSelf.overlayNode.image = generateBorderImage(theme: item.theme, bordered: false, selected: item.selected) + } + + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((90.0 - textLayout.size.width) / 2.0), y: 24.0), size: textLayout.size) + strongSelf.textNode.isHidden = item.emoticon != nil + + strongSelf.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + strongSelf.containerNode.frame = CGRect(origin: CGPoint(x: 15.0, y: -15.0), size: CGSize(width: 90.0, height: 120.0)) + + let _ = textApply() + let _ = emojiApply() + + let imageSize = CGSize(width: 82.0, height: 108.0) + strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: 4.0, y: 6.0), size: imageSize) + let applyLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: 9.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) + applyLayout() + + strongSelf.overlayNode.frame = strongSelf.imageNode.frame.insetBy(dx: -1.0, dy: -1.0) + strongSelf.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 79.0), size: CGSize(width: 90.0, height: 30.0)) + } + }) + } + } + + func crossfade() { + if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) { + self.view.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + super.animateInsertion(currentTimestamp, duration: duration, short: short) + + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + super.animateRemoved(currentTimestamp, duration: duration) + + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + + override func animateAdded(_ currentTimestamp: Double, duration: Double) { + super.animateAdded(currentTimestamp, duration: duration) + + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } +} + +final class ChatThemeScreen: ViewController { + private var controllerNode: ChatThemeScreenNode { + return self.displayNode as! ChatThemeScreenNode + } + + private var animatedIn = false + + private let context: AccountContext + private let initiallySelectedEmoticon: String? + private let dismissByTapOutside: Bool + private let previewTheme: (String?) -> Void + private let previewDarkTheme: (Bool) -> Void + private let completion: (String?) -> Void + + private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? + + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), initiallySelectedEmoticon: String?, dismissByTapOutside: Bool = true, previewTheme: @escaping (String?) -> Void, previewDarkTheme: @escaping (Bool) -> Void, completion: @escaping (String?) -> Void) { + self.context = context + self.presentationData = updatedPresentationData.initial + self.initiallySelectedEmoticon = initiallySelectedEmoticon + self.dismissByTapOutside = dismissByTapOutside + self.previewTheme = previewTheme + self.previewDarkTheme = previewDarkTheme + self.completion = completion + + super.init(navigationBarPresentationData: nil) + + self.statusBar.statusBarStyle = .Ignore + + self.blocksBackgroundWhenInOverlay = true + + self.presentationDataDisposable = (updatedPresentationData.signal + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + strongSelf.presentationData = presentationData + strongSelf.controllerNode.updatePresentationData(presentationData) + } + }) + + self.statusBar.statusBarStyle = .Ignore + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.presentationDataDisposable?.dispose() + } + + override public func loadDisplayNode() { + self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, initiallySelectedEmoticon: self.initiallySelectedEmoticon, dismissByTapOutside: self.dismissByTapOutside) + self.controllerNode.previewTheme = { [weak self] emoticon in + guard let strongSelf = self else { + return + } + strongSelf.previewTheme(emoticon ?? "") + } + self.controllerNode.present = { [weak self] c in + self?.present(c, in: .window(.root)) + } + self.controllerNode.previewDarkTheme = { [weak self] dark in + guard let strongSelf = self else { + return + } + strongSelf.previewDarkTheme(dark) + } + self.controllerNode.completion = { [weak self] emoticon in + guard let strongSelf = self else { + return + } + strongSelf.completion(emoticon) + strongSelf.dismiss() + } + self.controllerNode.dismiss = { [weak self] in + self?.presentingViewController?.dismiss(animated: false, completion: nil) + } + self.controllerNode.cancel = { [weak self] in + self?.previewTheme(nil) + self?.dismiss() + } + } + + override public func loadView() { + super.loadView() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.animatedIn { + self.animatedIn = true + self.controllerNode.animateIn() + } + } + + override public func dismiss(completion: (() -> Void)? = nil) { + self.forEachController({ controller in + if let controller = controller as? TooltipScreen { + controller.dismiss() + } + return true + }) + + self.controllerNode.animateOut(completion: completion) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + } +} + +class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { + private let context: AccountContext + private var presentationData: PresentationData + private let dismissByTapOutside: Bool + + private let dimNode: ASDisplayNode + private let wrappingScrollNode: ASScrollNode + private let contentContainerNode: ASDisplayNode + private let topContentContainerNode: SparseNode + private let effectNode: ASDisplayNode + private let backgroundNode: ASDisplayNode + private let contentBackgroundNode: ASDisplayNode + private let titleNode: ASTextNode + private let textNode: ImmediateTextNode + private let cancelButton: HighlightableButtonNode + private let switchThemeButton: HighlightTrackingButtonNode + private let animationNode: AnimationNode + private let doneButton: SolidRoundedButtonNode + + private let listNode: ListView + private var entries: [ThemeSettingsThemeEntry]? + private var enqueuedTransitions: [ThemeSettingsThemeItemNodeTransition] = [] + private var initialized = false + + private let initiallySelectedEmoticon: String? + private var selectedEmoticon: String? { + didSet { + self.selectedEmoticonPromise.set(self.selectedEmoticon) + } + } + private var selectedEmoticonPromise: ValuePromise + private var isDarkAppearancePromise: ValuePromise + + private var containerLayout: (ContainerViewLayout, CGFloat)? + + private let disposable = MetaDisposable() + + var present: ((ViewController) -> Void)? + var previewTheme: ((String?) -> Void)? + var previewDarkTheme: ((Bool) -> Void)? + var completion: ((String?) -> Void)? + var dismiss: (() -> Void)? + var cancel: (() -> Void)? + + init(context: AccountContext, presentationData: PresentationData, initiallySelectedEmoticon: String?, dismissByTapOutside: Bool) { + self.context = context + self.initiallySelectedEmoticon = initiallySelectedEmoticon + self.selectedEmoticon = initiallySelectedEmoticon + self.selectedEmoticonPromise = ValuePromise(initiallySelectedEmoticon) + self.presentationData = presentationData + self.dismissByTapOutside = dismissByTapOutside + + self.wrappingScrollNode = ASScrollNode() + self.wrappingScrollNode.view.alwaysBounceVertical = true + self.wrappingScrollNode.view.delaysContentTouches = false + self.wrappingScrollNode.view.canCancelContentTouches = true + + self.dimNode = ASDisplayNode() + self.dimNode.backgroundColor = .clear + + self.contentContainerNode = ASDisplayNode() + self.contentContainerNode.isOpaque = false + + self.topContentContainerNode = SparseNode() + self.topContentContainerNode.isOpaque = false + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.clipsToBounds = true + self.backgroundNode.cornerRadius = 16.0 + + self.isDarkAppearancePromise = ValuePromise(self.presentationData.theme.overallDarkAppearance) + + let backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor + let textColor = self.presentationData.theme.actionSheet.primaryTextColor + let blurStyle: UIBlurEffect.Style = self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark + + self.effectNode = ASDisplayNode(viewBlock: { + return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) + }) + + self.contentBackgroundNode = ASDisplayNode() + self.contentBackgroundNode.backgroundColor = backgroundColor + + let title = self.presentationData.strings.Conversation_Theme_Title + self.titleNode = ASTextNode() + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor) + + self.textNode = ImmediateTextNode() + + self.cancelButton = HighlightableButtonNode() + self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) + + var colors: [String: UIColor] = [:] + let accentColor = self.presentationData.theme.actionSheet.controlAccentColor + colors["Sunny.Path 14.Path.Stroke 1"] = accentColor + colors["Sunny.Path 15.Path.Stroke 1"] = accentColor + colors["Path.Path.Stroke 1"] = accentColor + colors["Sunny.Path 39.Path.Stroke 1"] = accentColor + colors["Sunny.Path 24.Path.Stroke 1"] = accentColor + colors["Sunny.Path 25.Path.Stroke 1"] = accentColor + colors["Sunny.Path 18.Path.Stroke 1"] = accentColor + colors["Sunny.Path 41.Path.Stroke 1"] = accentColor + colors["Sunny.Path 43.Path.Stroke 1"] = accentColor + colors["Path 10.Path.Fill 1"] = accentColor + colors["Path 11.Path.Fill 1"] = accentColor + + self.switchThemeButton = HighlightTrackingButtonNode() + self.animationNode = AnimationNode(animation: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: colors, scale: 1.0) + self.animationNode.isUserInteractionEnabled = false + + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) + self.doneButton.title = self.presentationData.strings.Conversation_Theme_Apply + + self.listNode = ListView() + self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + + super.init() + + self.backgroundColor = nil + self.isOpaque = false + + self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + self.addSubnode(self.dimNode) + + self.wrappingScrollNode.view.delegate = self + self.addSubnode(self.wrappingScrollNode) + + self.wrappingScrollNode.addSubnode(self.backgroundNode) + self.wrappingScrollNode.addSubnode(self.contentContainerNode) + self.wrappingScrollNode.addSubnode(self.topContentContainerNode) + + self.backgroundNode.addSubnode(self.effectNode) + self.backgroundNode.addSubnode(self.contentBackgroundNode) + self.contentContainerNode.addSubnode(self.titleNode) + self.contentContainerNode.addSubnode(self.textNode) + self.contentContainerNode.addSubnode(self.cancelButton) + self.contentContainerNode.addSubnode(self.doneButton) + + self.topContentContainerNode.addSubnode(self.animationNode) + self.topContentContainerNode.addSubnode(self.switchThemeButton) + self.topContentContainerNode.addSubnode(self.listNode) + + self.switchThemeButton.addTarget(self, action: #selector(self.switchThemePressed), forControlEvents: .touchUpInside) + self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) + self.doneButton.pressed = { [weak self] in + if let strongSelf = self { + strongSelf.doneButton.isUserInteractionEnabled = false + strongSelf.completion?(strongSelf.selectedEmoticon) + } + } + + self.disposable.set(combineLatest(queue: Queue.mainQueue(), self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager), self.selectedEmoticonPromise.get(), self.isDarkAppearancePromise.get()).start(next: { [weak self] themes, selectedEmoticon, isDarkAppearance in + guard let strongSelf = self else { + return + } + + let presentationData = strongSelf.presentationData + + var entries: [ThemeSettingsThemeEntry] = [] + if strongSelf.initiallySelectedEmoticon != nil { + entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, wallpaper: nil)) + } + for theme in themes { + entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, wallpaper: nil)) + } + + let action: (String?) -> Void = { [weak self] emoticon in + if let strongSelf = self { + strongSelf.crossfade() + + strongSelf.selectedEmoticon = emoticon + strongSelf.previewTheme?(emoticon) + let _ = ensureThemeVisible(listNode: strongSelf.listNode, emoticon: emoticon, animated: true) + + strongSelf.doneButton.title = emoticon == nil ? strongSelf.presentationData.strings.Conversation_Theme_Reset : strongSelf.presentationData.strings.Conversation_Theme_Apply + } + } + let previousEntries = strongSelf.entries ?? [] + let crossfade = previousEntries.count != entries.count + let transition = preparedTransition(context: strongSelf.context, action: action, from: previousEntries, to: entries, crossfade: crossfade) + strongSelf.enqueueTransition(transition) + + strongSelf.entries = entries + })) + + self.switchThemeButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.animationNode.layer.removeAnimation(forKey: "opacity") + strongSelf.animationNode.alpha = 0.4 + } else { + strongSelf.animationNode.alpha = 1.0 + strongSelf.animationNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) { + self.enqueuedTransitions.append(transition) + + while !self.enqueuedTransitions.isEmpty { + self.dequeueTransition() + } + } + + private func dequeueTransition() { + guard let transition = self.enqueuedTransitions.first else { + return + } + self.enqueuedTransitions.remove(at: 0) + + var options = ListViewDeleteAndInsertOptions() + if self.initialized && transition.crossfade { + options.insert(.AnimateCrossfade) + } + options.insert(.Synchronous) + + var scrollToItem: ListViewScrollToItem? + if !self.initialized { + if let index = transition.entries.firstIndex(where: { entry in + return entry.emoticon == self.initiallySelectedEmoticon + }) { + scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) + self.initialized = true + } + } + + self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in + }) + } + + func updatePresentationData(_ presentationData: PresentationData) { + let previousTheme = self.presentationData.theme + self.presentationData = presentationData + + self.isDarkAppearancePromise.set(presentationData.theme.overallDarkAppearance) + + if let effectView = self.effectNode.view as? UIVisualEffectView { + effectView.effect = UIBlurEffect(style: presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark) + } + + self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor + self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) + + if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + + self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) + self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) + } + + override func didLoad() { + super.didLoad() + + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never + } + + self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true + } + + @objc func cancelButtonPressed() { + self.cancel?() + } + + @objc func switchThemePressed() { + var colors: [String: UIColor] = [:] + let accentColor = self.presentationData.theme.actionSheet.controlAccentColor + colors["Sunny.Path 14.Path.Stroke 1"] = accentColor + colors["Sunny.Path 15.Path.Stroke 1"] = accentColor + colors["Path.Path.Stroke 1"] = accentColor + colors["Sunny.Path 39.Path.Stroke 1"] = accentColor + colors["Sunny.Path 24.Path.Stroke 1"] = accentColor + colors["Sunny.Path 25.Path.Stroke 1"] = accentColor + colors["Sunny.Path 18.Path.Stroke 1"] = accentColor + colors["Sunny.Path 41.Path.Stroke 1"] = accentColor + colors["Sunny.Path 43.Path.Stroke 1"] = accentColor + colors["Path 10.Path.Fill 1"] = accentColor + colors["Path 11.Path.Fill 1"] = accentColor + + self.crossfade() + self.animationNode.setAnimation(name: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: colors) + self.animationNode.playOnce() + self.previewDarkTheme?(!self.presentationData.theme.overallDarkAppearance) + } + + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if self.dismissByTapOutside, case .ended = recognizer.state { + self.cancelButtonPressed() + } + } + + private func crossfade() { + if let snapshotView = self.backgroundNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.backgroundNode.frame + self.backgroundNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.backgroundNode.view) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + + if let snapshotView = self.contentContainerNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.contentContainerNode.frame + self.contentContainerNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.contentContainerNode.view) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + + self.listNode.forEachVisibleItemNode { node in + if let node = node as? ThemeSettingsThemeItemIconNode { + node.crossfade() + } + } + } + + private var animatedOut = false + func animateIn() { + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + + let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY + let dimPosition = self.dimNode.layer.position + + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + let targetBounds = self.bounds + self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset) + self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset) + transition.animateView({ + self.bounds = targetBounds + self.dimNode.position = dimPosition + }) + + let frame = self.switchThemeButton.view.convert(self.switchThemeButton.bounds, to: self.view) + + Queue.mainQueue().after(1.0) { + if !self.animatedOut { + self.present?(TooltipScreen(text: self.presentationData.theme.overallDarkAppearance ? self.presentationData.strings.Conversation_Theme_SwitchToLight : self.presentationData.strings.Conversation_Theme_SwitchToDark, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + return .dismiss(consume: false) + })) + } + } + } + + func animateOut(completion: (() -> Void)? = nil) { + self.animatedOut = true + + var dimCompleted = false + var offsetCompleted = false + + let internalCompletion: () -> Void = { [weak self] in + if let strongSelf = self, dimCompleted && offsetCompleted { + strongSelf.dismiss?() + } + completion?() + } + + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + dimCompleted = true + internalCompletion() + }) + + let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY + let dimPosition = self.dimNode.layer.position + self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + offsetCompleted = true + internalCompletion() + }) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.bounds.contains(point) { + if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) { + return self.dimNode.view + } + } + return super.hitTest(point, with: event) + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + let contentOffset = scrollView.contentOffset + let additionalTopHeight = max(0.0, -contentOffset.y) + + if additionalTopHeight >= 30.0 { + self.cancelButtonPressed() + } + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.containerLayout = (layout, navigationBarHeight) + + var insets = layout.insets(options: [.statusBar, .input]) + let cleanInsets = layout.insets(options: [.statusBar]) + insets.top = max(10.0, insets.top) + + let bottomInset: CGFloat = 10.0 + cleanInsets.bottom + let titleHeight: CGFloat = 54.0 + let contentHeight = titleHeight + bottomInset + 188.0 + + let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) + + let sideInset = floor((layout.size.width - width) / 2.0) + let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) + let contentFrame = contentContainerFrame + + var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0)) + if backgroundFrame.minY < contentFrame.minY { + backgroundFrame.origin.y = contentFrame.minY + } + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + + let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight)) + let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0), size: titleSize) + transition.updateFrame(node: self.titleNode, frame: titleFrame) + + let switchThemeSize = CGSize(width: 44.0, height: 44.0) + let switchThemeFrame = CGRect(origin: CGPoint(x: 3.0, y: 6.0), size: switchThemeSize) + transition.updateFrame(node: self.switchThemeButton, frame: switchThemeFrame) + transition.updateFrame(node: self.animationNode, frame: switchThemeFrame.insetBy(dx: 9.0, dy: 9.0)) + + let cancelSize = CGSize(width: 44.0, height: 44.0) + let cancelFrame = CGRect(origin: CGPoint(x: contentFrame.width - cancelSize.width - 3.0, y: 6.0), size: cancelSize) + transition.updateFrame(node: self.cancelButton, frame: cancelFrame) + + let buttonInset: CGFloat = 16.0 + let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) + transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 6.0, width: contentFrame.width, height: doneButtonHeight)) + + transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) + transition.updateFrame(node: self.topContentContainerNode, frame: contentContainerFrame) + + var listInsets = UIEdgeInsets() + listInsets.top += layout.safeInsets.left + 12.0 + listInsets.bottom += layout.safeInsets.right + 12.0 + + let contentSize = CGSize(width: contentFrame.width, height: 120.0) + + self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width) + self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight + 6.0) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } +} diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index d3edf3e890..f15859c9d6 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -49,6 +49,9 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if params.activateInput { controller.activateInput() } + if params.changeColors { + controller.presentThemeSelection() + } if let botStart = params.botStart { controller.updateChatPresentationInterfaceState(interactive: false, { state -> ChatPresentationInterfaceState in return state.updatedBotStartPayload(botStart.payload) @@ -116,6 +119,9 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if params.activateInput { controller.activateInput() } + if params.changeColors { + controller.presentThemeSelection() + } } params.navigationController.currentWindow?.forEachController { controller in diff --git a/submodules/TelegramUI/Sources/OpenAddContact.swift b/submodules/TelegramUI/Sources/OpenAddContact.swift index cead38120e..86f0bce75a 100644 --- a/submodules/TelegramUI/Sources/OpenAddContact.swift +++ b/submodules/TelegramUI/Sources/OpenAddContact.swift @@ -17,7 +17,7 @@ func openAddContactImpl(context: AccountContext, firstName: String = "", lastNam let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: label, value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") present(deviceContactInfoController(context: context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in if let peer = peer { - if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { pushController(infoController) } } else { diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 240a799b53..9f3775d5f6 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -192,7 +192,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur case .info: let _ = (context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { peer in - if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { context.sharedContext.applicationBindings.dismissNativeController() navigationController?.pushViewController(infoController) } @@ -480,7 +480,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur return transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt32Value(idValue))) } |> deliverOnMainQueue).start(next: { peer in - if let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { navigationController?.pushViewController(controller) } }) diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index ee4c88a016..56d805c414 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -186,7 +186,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu self.isGlobalSearch = false } - self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, source: source, subject: .message(id: initialMessageId, highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch)) + self.historyNode = ChatHistoryListNode(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, source: source, subject: .message(id: initialMessageId, highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch)) self.historyNode.clipsToBounds = true super.init() @@ -528,7 +528,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu } let chatLocationContextHolder = Atomic(value: nil) - let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(id: messageId, highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch)) + let historyNode = ChatHistoryListNode(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(id: messageId, highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch)) historyNode.clipsToBounds = true historyNode.preloadPages = true historyNode.stackFromBottom = true diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index caaa6c1401..f90d3f1a32 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -59,7 +59,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { self.selectedMessagesPromise.set(.single(self.selectedMessages)) let chatLocationContextHolder = Atomic(value: nil) - self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) + self.listNode = ChatHistoryListNode(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) self.listNode.clipsToBounds = true self.listNode.defaultToSynchronousTransactionWhileScrolling = true self.listNode.scroller.bounces = false diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 3fde267518..670dc56abc 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -348,7 +348,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { let separatorNode: ASDisplayNode let backgroundNode: NavigationBackgroundNode - init(context: AccountContext, peerId: PeerId, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, reportMessages: @escaping () -> Void) { + init(context: AccountContext, presentationData: PresentationData, peerId: PeerId, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, reportMessages: @escaping () -> Void) { self.context = context self.peerId = peerId self.deleteMessages = deleteMessages @@ -356,7 +356,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { self.forwardMessages = forwardMessages self.reportMessages = reportMessages - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let presentationData = presentationData self.separatorNode = ASDisplayNode() self.backgroundNode = NavigationBackgroundNode(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor) @@ -1571,7 +1571,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD self.peerId = peerId self.isOpenedFromChat = isOpenedFromChat self.videoCallsEnabled = VideoCallsConfiguration(appConfiguration: context.currentAppConfiguration.with { $0 }).areVideoCallsEnabled - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationData = controller.presentationData self.nearbyPeerDistance = nearbyPeerDistance self.callMessages = callMessages self.isSettings = isSettings @@ -3146,7 +3146,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { strongSelf.controller?.push(infoController) } } @@ -3220,7 +3220,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { (strongSelf.controller?.navigationController as? NavigationController)?.pushViewController(infoController) } } @@ -3484,6 +3484,24 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD let headerButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: self.headerNode.isAvatarExpanded, videoCallsEnabled: self.videoCallsEnabled, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false)) let filteredButtons = allHeaderButtons.subtracting(headerButtons) + + var canChangeColors = true + if let peer = peer as? TelegramChannel { + canChangeColors = peer.hasPermission(.changeInfo) + } else if let peer = peer as? TelegramGroup, case .member = peer.role { + canChangeColors = !peer.hasBannedPermission(.banChangeInfo) + } + + if canChangeColors { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ChangeColors, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openChatForThemeChange() + }))) + } + if filteredButtons.contains(.addMember) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonAddMember, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) @@ -3609,7 +3627,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } }))) } - + if self.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_StartSecretChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) @@ -3794,6 +3812,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } } + private func openChatForThemeChange() { + if let navigationController = (self.controller?.navigationController as? NavigationController) { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.peerId), keepStack: .default, changeColors: true, completion: { _ in + })) + } + } + private func openStartSecretChat() { let peerId = self.peerId let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, PeerId?) in @@ -4776,7 +4801,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD if isMember { mode = .group(self.peerId) } - if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, peer: peer, mode: mode, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: false, fromChat: false) { (self.controller?.navigationController as? NavigationController)?.pushViewController(infoController) } } @@ -5497,7 +5522,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } })) } - + fileprivate func switchToAccount(id: AccountRecordId) { self.accountsAndPeers.set(.never()) self.context.sharedContext.switchToAccount(id: id, fromSettingsController: nil, withChatListController: nil) @@ -6054,7 +6079,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD selectionPanelNode = current } else { wasAdded = true - selectionPanelNode = PeerInfoSelectionPanelNode(context: self.context, peerId: self.peerId, deleteMessages: { [weak self] in + selectionPanelNode = PeerInfoSelectionPanelNode(context: self.context, presentationData: self.presentationData, peerId: self.peerId, deleteMessages: { [weak self] in guard let strongSelf = self else { return } @@ -6452,7 +6477,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { private let isSettings: Bool private let ignoreGroupInCommon: PeerId? - private var presentationData: PresentationData + fileprivate var presentationData: PresentationData private var presentationDataDisposable: Disposable? private let accountsAndPeers = Promise<((AccountContext, Peer)?, [(AccountContext, Peer, Int32)])>() @@ -6474,7 +6499,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? - public init(context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, callMessages: [Message], isSettings: Bool = false, ignoreGroupInCommon: PeerId? = nil) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, callMessages: [Message], isSettings: Bool = false, ignoreGroupInCommon: PeerId? = nil) { self.context = context self.peerId = peerId self.avatarInitiallyExpanded = avatarInitiallyExpanded @@ -6484,7 +6509,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { self.isSettings = isSettings self.ignoreGroupInCommon = ignoreGroupInCommon - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationData = updatedPresentationData?.0 ?? context.sharedContext.currentPresentationData.with { $0 } let baseNavigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) super.init(navigationBarPresentationData: NavigationBarPresentationData( @@ -6707,7 +6732,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen { self?.controllerNode.scrollToTop() } - self.presentationDataDisposable = (context.sharedContext.presentationData + self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { let previousTheme = strongSelf.presentationData.theme diff --git a/submodules/TelegramUI/Sources/PollResultsController.swift b/submodules/TelegramUI/Sources/PollResultsController.swift index 1e418571bf..e669d85ee7 100644 --- a/submodules/TelegramUI/Sources/PollResultsController.swift +++ b/submodules/TelegramUI/Sources/PollResultsController.swift @@ -349,7 +349,7 @@ public func pollResultsController(context: AccountContext, messageId: MessageId, }) }, openPeer: { peer in if let peer = peer.peers[peer.peerId] { - if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { pushControllerImpl?(controller) } } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index d88a1b68dc..a58ea0267c 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1103,8 +1103,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { handleTextLinkActionImpl(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink) } - public func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController? { - let controller = peerInfoControllerImpl(context: context, peer: peer, mode: mode, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: fromChat) + public func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController? { + let controller = peerInfoControllerImpl(context: context, updatedPresentationData: updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: fromChat) controller?.navigationPresentation = .modalInLargeLayout return controller } @@ -1419,11 +1419,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { private let defaultChatControllerInteraction = ChatControllerInteraction.default -private func peerInfoControllerImpl(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool) -> ViewController? { +private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool) -> ViewController? { if let _ = peer as? TelegramGroup { - return PeerInfoScreenImpl(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, callMessages: []) + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, callMessages: []) } else if let _ = peer as? TelegramChannel { - return PeerInfoScreenImpl(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, callMessages: []) + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, callMessages: []) } else if peer is TelegramUser { var nearbyPeerDistance: Int32? var callMessages: [Message] = [] @@ -1438,9 +1438,9 @@ private func peerInfoControllerImpl(context: AccountContext, peer: Peer, mode: P case let .group(id): ignoreGroupInCommon = id } - return PeerInfoScreenImpl(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, callMessages: callMessages, ignoreGroupInCommon: ignoreGroupInCommon) + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, callMessages: callMessages, ignoreGroupInCommon: ignoreGroupInCommon) } else if peer is TelegramSecretChat { - return PeerInfoScreenImpl(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, callMessages: []) + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, callMessages: []) } return nil } diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index c00b62f1bd..ad07e31c0d 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -111,7 +111,7 @@ public final class TelegramRootController: NavigationController { sharedContext.switchingData = (nil, nil, nil) } - let accountSettingsController = PeerInfoScreenImpl(context: self.context, peerId: self.context.account.peerId, avatarInitiallyExpanded: false, isOpenedFromChat: false, nearbyPeerDistance: nil, callMessages: [], isSettings: true) + let accountSettingsController = PeerInfoScreenImpl(context: self.context, updatedPresentationData: nil, peerId: self.context.account.peerId, avatarInitiallyExpanded: false, isOpenedFromChat: false, nearbyPeerDistance: nil, callMessages: [], isSettings: true) accountSettingsController.tabBarItemDebugTapAction = { [weak self, weak accountSettingsController] in guard let strongSelf = self, let accountSettingsController = accountSettingsController else { return diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index ec50524c49..b5aa28163e 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -34,7 +34,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate peerSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in if let controller = controller, let peer = peer { - if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { (controller.navigationController as? NavigationController)?.pushViewController(infoController) } } diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index cd1f4e00c7..8d595bb935 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -53,14 +53,17 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private var isArrowInverted: Bool = false + private let inset: CGFloat + private var validLayout: ContainerViewLayout? - init(text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) { + init(text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) { self.tooltipStyle = style self.icon = icon self.customContentNode = customContentNode self.location = location self.displayDuration = displayDuration + self.inset = inset self.shouldDismissOnTouch = shouldDismissOnTouch self.requestDismiss = requestDismiss self.openActiveTextItem = openActiveTextItem @@ -295,7 +298,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { self.scrollingContainer.frame = CGRect(origin: CGPoint(), size: layout.size) - let sideInset: CGFloat = 13.0 + layout.safeInsets.left + let sideInset: CGFloat = self.inset + layout.safeInsets.left let bottomInset: CGFloat = 10.0 let contentInset: CGFloat = 11.0 let contentVerticalInset: CGFloat = 11.0 @@ -563,6 +566,7 @@ public final class TooltipScreen: ViewController { private let customContentNode: TooltipCustomContentNode? private let location: TooltipScreen.Location private let displayDuration: DisplayDuration + private let inset: CGFloat private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch private let openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? @@ -578,7 +582,7 @@ public final class TooltipScreen: ViewController { private var dismissTimer: Foundation.Timer? - public init(text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) { + public init(text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) { self.text = text self.textEntities = textEntities self.style = style @@ -586,6 +590,7 @@ public final class TooltipScreen: ViewController { self.customContentNode = customContentNode self.location = location self.displayDuration = displayDuration + self.inset = inset self.shouldDismissOnTouch = shouldDismissOnTouch self.openActiveTextItem = openActiveTextItem @@ -642,7 +647,7 @@ public final class TooltipScreen: ViewController { } override public func loadDisplayNode() { - self.displayNode = TooltipScreenNode(text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in + self.displayNode = TooltipScreenNode(text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 97a85afecd..24d4d8e419 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -1291,7 +1291,7 @@ public func themeImage(account: Account, accountManager: AccountManager, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func themeIconImage(account: Account, accountManager: AccountManager, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, emoticon: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Int32?), NoError> var reference: MediaResourceReference? @@ -1468,21 +1468,71 @@ public func themeIconImage(account: Account, accountManager: AccountManager= 2 { + let gradientColors = incomingColors.map { $0.cgColor } as CFArray + + var locations: [CGFloat] = [] + for i in 0 ..< incomingColors.count { + let t = CGFloat(i) / CGFloat(incomingColors.count - 1) + locations.append(t) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions()) + } else if !incomingColors.isEmpty { + c.setFillColor(incomingColors[0].cgColor) + c.fill(rect) + } + + c.resetClip() + } else { + let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors) + 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)) + + let outgoingColors = colors.2 + if emoticon { + let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0) + c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath) + c.clip() + + if outgoingColors.count >= 2 { + let gradientColors = outgoingColors.map { $0.cgColor } as CFArray + + var locations: [CGFloat] = [] + for i in 0 ..< outgoingColors.count { + let t = CGFloat(i) / CGFloat(outgoingColors.count - 1) + locations.append(t) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions()) + } else if !outgoingColors.isEmpty { + c.setFillColor(outgoingColors[0].cgColor) + c.fill(rect) + } + + c.resetClip() + } else { + let outgoing = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: outgoingColors) + c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0)) + } } addCorners(context, arguments: arguments) return context diff --git a/submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift b/submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift index 622b8cde32..c358864c6a 100644 --- a/submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift +++ b/submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift @@ -269,7 +269,7 @@ public func widgetSetupScreen(context: AccountContext) -> ViewController { return transaction.getPeer(peerId) } |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) else { + guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) else { return } pushControllerImpl?(controller) From 89bbb021d432be9a77c3a07d4d0f7f5c0ad66e7b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 22 Aug 2021 19:15:52 +0300 Subject: [PATCH 2/4] Temporary fi x --- submodules/TelegramUI/Sources/ChatController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index e9facb3e5a..e55a8b4209 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3769,7 +3769,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } |> distinctUntilChanged - self.presentationDataDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, context.engine.themes.getChatThemes(accountManager: context.sharedContext.accountManager, onlyCached: true), themeEmoticon, self.themeEmoticonPreviewPromise.get(), self.themeDarkAppearancePreviewPromise.get()).start(next: { [weak self] presentationData, chatThemes, themeEmoticon, themeEmoticonPreview, darkAppearancePreview in + self.presentationDataDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, context.engine.themes.getChatThemes(accountManager: context.sharedContext.accountManager, onlyCached: false), themeEmoticon, self.themeEmoticonPreviewPromise.get(), self.themeDarkAppearancePreviewPromise.get()).start(next: { [weak self] presentationData, chatThemes, themeEmoticon, themeEmoticonPreview, darkAppearancePreview in if let strongSelf = self { let previousTheme = strongSelf.presentationData.theme let previousStrings = strongSelf.presentationData.strings From 2e8af641820f368bff1a883d573f48e5ed59ad91 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 22 Aug 2021 19:39:29 +0300 Subject: [PATCH 3/4] Chat-specific theme improvements --- .../State/AccountStateManagementUtils.swift | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 95dfecbd39..695a61b06d 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -2447,6 +2447,28 @@ func replayFinalState(accountManager: AccountManager Date: Sun, 22 Aug 2021 23:59:27 +0300 Subject: [PATCH 4/4] Chat-specific theme improvements --- .../TelegramUI/Sources/ChatController.swift | 21 +++-- .../TelegramUI/Sources/ChatThemeScreen.swift | 88 +++++++++++-------- 2 files changed, 64 insertions(+), 45 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index e55a8b4209..035c3c8c3a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3783,19 +3783,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G themeEmoticon = nil } } - - var isDarkAppearance = presentationData.theme.overallDarkAppearance - if let darkAppearancePreview = darkAppearancePreview { - isDarkAppearance = darkAppearancePreview - } - + var presentationData = presentationData if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) { + var isDarkAppearance = presentationData.theme.overallDarkAppearance + if let darkAppearancePreview = darkAppearancePreview { + isDarkAppearance = darkAppearancePreview + } let customTheme = isDarkAppearance ? theme.darkTheme : theme.theme if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) { presentationData = presentationData.withUpdated(theme: theme) presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper) } + } else if let darkAppearancePreview = darkAppearancePreview { + let lightTheme: PresentationTheme + let darkTheme: PresentationTheme + + if presentationData.theme.overallDarkAppearance { + darkTheme = presentationData.theme + } else { + lightTheme = presentationData.theme + } } strongSelf.presentationData = presentationData @@ -12942,6 +12950,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.themeDarkAppearancePreviewPromise.set(dark) } }, completion: { [weak self] emoticon in + strongSelf.presentCrossfadeSnapshot() strongSelf.themeDarkAppearancePreviewPromise.set(nil) let _ = context.engine.themes.setChatTheme(peerId: peerId, emoji: emoticon).start(completed: { [weak self] in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 9f4ecc1a38..9a1958ba17 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -423,15 +423,18 @@ final class ChatThemeScreen: ViewController { guard let strongSelf = self else { return } - strongSelf.completion(emoticon) strongSelf.dismiss() + strongSelf.completion(emoticon) } self.controllerNode.dismiss = { [weak self] in self?.presentingViewController?.dismiss(animated: false, completion: nil) } self.controllerNode.cancel = { [weak self] in - self?.previewTheme(nil) - self?.dismiss() + guard let strongSelf = self else { + return + } + strongSelf.dismiss() + strongSelf.previewTheme(nil) } } @@ -466,7 +469,24 @@ final class ChatThemeScreen: ViewController { } } -class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { +private func iconColors(theme: PresentationTheme) -> [String: UIColor] { + let accentColor = theme.actionSheet.controlAccentColor + var colors: [String: UIColor] = [:] + colors["Sunny.Path 14.Path.Stroke 1"] = accentColor + colors["Sunny.Path 15.Path.Stroke 1"] = accentColor + colors["Path.Path.Stroke 1"] = accentColor + colors["Sunny.Path 39.Path.Stroke 1"] = accentColor + colors["Sunny.Path 24.Path.Stroke 1"] = accentColor + colors["Sunny.Path 25.Path.Stroke 1"] = accentColor + colors["Sunny.Path 18.Path.Stroke 1"] = accentColor + colors["Sunny.Path 41.Path.Stroke 1"] = accentColor + colors["Sunny.Path 43.Path.Stroke 1"] = accentColor + colors["Path 10.Path.Fill 1"] = accentColor + colors["Path 11.Path.Fill 1"] = accentColor + return colors +} + +private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { private let context: AccountContext private var presentationData: PresentationData private let dismissByTapOutside: Bool @@ -558,22 +578,8 @@ class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { self.cancelButton = HighlightableButtonNode() self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) - var colors: [String: UIColor] = [:] - let accentColor = self.presentationData.theme.actionSheet.controlAccentColor - colors["Sunny.Path 14.Path.Stroke 1"] = accentColor - colors["Sunny.Path 15.Path.Stroke 1"] = accentColor - colors["Path.Path.Stroke 1"] = accentColor - colors["Sunny.Path 39.Path.Stroke 1"] = accentColor - colors["Sunny.Path 24.Path.Stroke 1"] = accentColor - colors["Sunny.Path 25.Path.Stroke 1"] = accentColor - colors["Sunny.Path 18.Path.Stroke 1"] = accentColor - colors["Sunny.Path 41.Path.Stroke 1"] = accentColor - colors["Sunny.Path 43.Path.Stroke 1"] = accentColor - colors["Path 10.Path.Fill 1"] = accentColor - colors["Path 11.Path.Fill 1"] = accentColor - self.switchThemeButton = HighlightTrackingButtonNode() - self.animationNode = AnimationNode(animation: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: colors, scale: 1.0) + self.animationNode = AnimationNode(animation: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0) self.animationNode.isUserInteractionEnabled = false self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) @@ -633,9 +639,9 @@ class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { } let action: (String?) -> Void = { [weak self] emoticon in - if let strongSelf = self { - strongSelf.crossfade() - + if let strongSelf = self, strongSelf.selectedEmoticon != emoticon { + strongSelf.animateCrossfade(updateSunIcon: true) + strongSelf.selectedEmoticon = emoticon strongSelf.previewTheme?(emoticon) let _ = ensureThemeVisible(listNode: strongSelf.listNode, emoticon: emoticon, animated: true) @@ -699,6 +705,9 @@ class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { } func updatePresentationData(_ presentationData: PresentationData) { + guard !self.animatedOut else { + return + } let previousTheme = self.presentationData.theme self.presentationData = presentationData @@ -717,6 +726,12 @@ class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) + + if self.animationNode.isPlaying { + + } else { + self.animationNode.setAnimation(name: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) + } } override func didLoad() { @@ -734,22 +749,8 @@ class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { } @objc func switchThemePressed() { - var colors: [String: UIColor] = [:] - let accentColor = self.presentationData.theme.actionSheet.controlAccentColor - colors["Sunny.Path 14.Path.Stroke 1"] = accentColor - colors["Sunny.Path 15.Path.Stroke 1"] = accentColor - colors["Path.Path.Stroke 1"] = accentColor - colors["Sunny.Path 39.Path.Stroke 1"] = accentColor - colors["Sunny.Path 24.Path.Stroke 1"] = accentColor - colors["Sunny.Path 25.Path.Stroke 1"] = accentColor - colors["Sunny.Path 18.Path.Stroke 1"] = accentColor - colors["Sunny.Path 41.Path.Stroke 1"] = accentColor - colors["Sunny.Path 43.Path.Stroke 1"] = accentColor - colors["Path 10.Path.Fill 1"] = accentColor - colors["Path 11.Path.Fill 1"] = accentColor - - self.crossfade() - self.animationNode.setAnimation(name: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: colors) + self.animateCrossfade() + self.animationNode.setAnimation(name: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) self.animationNode.playOnce() self.previewDarkTheme?(!self.presentationData.theme.overallDarkAppearance) } @@ -760,7 +761,16 @@ class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { } } - private func crossfade() { + private func animateCrossfade(updateSunIcon: Bool = false) { + if let snapshotView = self.animationNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.animationNode.frame + self.animationNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.animationNode.view) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + if let snapshotView = self.backgroundNode.view.snapshotView(afterScreenUpdates: false) { snapshotView.frame = self.backgroundNode.frame self.backgroundNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.backgroundNode.view)