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/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift index f492ef9884..452362d86f 100644 --- a/Telegram/SiriIntents/IntentHandler.swift +++ b/Telegram/SiriIntents/IntentHandler.swift @@ -34,7 +34,7 @@ private struct ApplicationSettings { let logging: LoggingSettings } -private func applicationSettings(accountManager: AccountManager) -> Signal { +private func applicationSettings(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> ApplicationSettings in let loggingSettings: LoggingSettings if let value = transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings { @@ -79,7 +79,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo private let searchDisposable = MetaDisposable() private var rootPath: String? - private var accountManager: AccountManager? + private var accountManager: AccountManager? private var encryptionParameters: ValueBoxEncryptionParameters? private var appGroupUrl: URL? @@ -121,7 +121,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" initializeAccountManagement() - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false) + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false) self.accountManager = accountManager let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) @@ -134,25 +134,22 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo var result: [(AccountRecordId, Int, PeerId, Bool)] = [] for record in view.records { let isLoggedOut = record.attributes.contains(where: { attribute in - return attribute is LoggedOutAccountAttribute - }) - if isLoggedOut { - continue - } - /*let isTestingEnvironment = record.attributes.contains(where: { attribute in - if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment { + if case .loggedOut = attribute { return true } else { return false } - })*/ + }) + if isLoggedOut { + continue + } var backupData: AccountBackupData? var sortIndex: Int32 = 0 for attribute in record.attributes { - if let attribute = attribute as? AccountSortOrderAttribute { - sortIndex = attribute.order - } else if let attribute = attribute as? AccountBackupDataAttribute { - backupData = attribute.data + if case let .sortOrder(sortOrder) = attribute { + sortIndex = sortOrder.order + } else if case let .backupData(backupDataValue) = attribute { + backupData = backupDataValue.data } } if let backupData = backupData { @@ -894,12 +891,16 @@ private final class WidgetIntentHandler { let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!) self.encryptionParameters = encryptionParameters - let view = AccountManager.getCurrentRecords(basePath: rootPath + "/accounts-metadata") + let view = AccountManager.getCurrentRecords(basePath: rootPath + "/accounts-metadata") var result: [(AccountRecordId, Int, PeerId, Bool)] = [] for record in view.records { let isLoggedOut = record.attributes.contains(where: { attribute in - return attribute is LoggedOutAccountAttribute + if case .loggedOut = attribute { + return true + } else { + return false + } }) if isLoggedOut { continue @@ -907,10 +908,10 @@ private final class WidgetIntentHandler { var backupData: AccountBackupData? var sortIndex: Int32 = 0 for attribute in record.attributes { - if let attribute = attribute as? AccountSortOrderAttribute { - sortIndex = attribute.order - } else if let attribute = attribute as? AccountBackupDataAttribute { - backupData = attribute.data + if case let .sortOrder(sortOrder) = attribute { + sortIndex = sortOrder.order + } else if case let .backupData(backupDataValue) = attribute { + backupData = backupDataValue.data } } if let backupData = backupData { diff --git a/Telegram/Telegram-iOS/Resources/chunk2.ch2.q1.mp4 b/Telegram/Telegram-iOS/Resources/chunk2.ch2.q1.mp4 new file mode 100644 index 0000000000..b513f5f83d Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/chunk2.ch2.q1.mp4 differ diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 818c215c06..237ffaaca7 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -6569,7 +6569,102 @@ Sorry for the inconvenience."; "Gallery.VideoSaved" = "Video Saved"; "Gallery.WaitForVideoDownoad" = "Please wait for the video to be fully downloaded."; +"Gallery.SaveImage" = "Save Image"; +"Gallery.SaveVideo" = "Save Video"; + "VoiceChat.VideoParticipantsLimitExceededExtended" = "The voice chat is over %@ members.\nNew participants only have access to audio stream. "; "PlaybackSpeed.Title" = "Playback Speed"; "PlaybackSpeed.Normal" = "Normal"; + +"Chat.NextChannelSameLocationSwipeProgress" = "Swipe up to go to the next unread channel"; +"Chat.NextChannelSameLocationSwipeAction" = "Release to go to the next unread channel"; +"Chat.NextChannelFolderSwipeProgress" = "Swipe up to go to the %@ folder"; +"Chat.NextChannelFolderSwipeAction" = "Release to go to the %@ folder"; +"Chat.NextChannelArchivedSwipeProgress" = "Swipe up to go to archived channels"; +"Chat.NextChannelArchivedSwipeAction" = "Release to go to archived channels"; +"Chat.NextChannelUnarchivedSwipeProgress" = "Swipe up to go to unarchived channels"; +"Chat.NextChannelUnarchivedSwipeAction" = "Release to go to unarchived channels"; + +"Conversation.ForwardOptions.Text" = "What whould you like to do with %1$@ from %2$@?"; +"Conversation.ForwardOptions.TextPersonal" = "What whould you like to do with %1$@ from your chat with %2$@?"; +"Conversation.ForwardOptions.ForwardToAnotherChat" = "Forward to Another Chat"; +"Conversation.ForwardOptions.CancelForwarding" = "Cancel Forwarding"; +"Conversation.ForwardOptions.HideSendersNames" = "Hide Senders' Names"; +"Conversation.ForwardOptions.ShowSendersNames" = "Show Senders' Names"; + +"Conversation.ForwardOptions.Title_1" = "%@ Message"; +"Conversation.ForwardOptions.Title_2" = "%@ Messages"; +"Conversation.ForwardOptions.Title_3_10" = "%@ Messages"; +"Conversation.ForwardOptions.Title_any" = "%@ Messages"; +"Conversation.ForwardOptions.Title_many" = "%@ Messages"; +"Conversation.ForwardOptions.Title_0" = "%@ Messages"; + +"Conversation.ForwardOptions.Messages_1" = "%@ message"; +"Conversation.ForwardOptions.Messages_2" = "%@ messages"; +"Conversation.ForwardOptions.Messages_3_10" = "%@ messages"; +"Conversation.ForwardOptions.Messages_any" = "%@ messages"; +"Conversation.ForwardOptions.Messages_many" = "%@ messages"; +"Conversation.ForwardOptions.Messages_0" = "%@ messages"; + +"Conversation.ForwardOptions.You" = "You (senders' names hidden)"; + +"Activity.ChoosingSticker" = "choosing sticker"; +"WallpaperPreview.Animate" = "Animate"; +"WallpaperPreview.AnimateDescription" = "Colors will move when you send messages"; + +"Username.InvalidStartsWithUnderscore" = "Sorry, a username can't start with an underscore."; +"Username.InvalidEndsWithUnderscore" = "Sorry, a username can't end with an underscore."; + +"Channel.Username.InvalidStartsWithUnderscore" = "Channel names can't start with an underscore."; +"Channel.Username.InvalidEndsWithUnderscore" = "Channel names can't end with an underscore."; + +"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."; +"Appstore.Creative.Chat" = "**You**\nSend a dice emoji to roll a die!\n**You**\nAdvance to Illinois Ave. If you pass Go, collect coffee\n**Gabriella**\nPassed!\n**You**\nOkay\nWait for me there."; +"Appstore.Creative.Chat.Name" = "**Gabriella**"; +"Appstore.Fast" = "**Fast**\nSimple, reliable and synced\nacross all your devices."; +"Appstore.Fast.Chat1" = "**Alicia Torreaux**\nBob says hi."; +"Appstore.Fast.Chat2" = "**Roberto**\nSay hello to Alice."; +"Appstore.Fast.Chat3" = "**Digital Nomads**\nJennie\nWe just reached 2,500 members! WOO!"; +"Appstore.Fast.Chat4" = "**Veronica**\nTable for four, 2 PM. Be there."; +"Appstore.Fast.Chat5" = "**Animal Videos**\nVote now! Moar cat videos in this channel?"; +"Appstore.Fast.Chat6" = "**Little Sister**\nDon't tell mom yet, but I got the job! I'm going to ROME!"; +"Appstore.Fast.Chat7" = "**James**\nCheck these out"; +"Appstore.Fast.Chat8" = "**Study Group**\nEmma\nSticker"; +"Appstore.Fast.Chat9" = "**Digital Nomads**"; +"Appstore.Free.Chat" = "**Jessica**\nPaper airplane is lyfted by...\nWings\nPropeller\n**You**\nIs this from Monday’s test?\n**Harry**\nOnlinePartyPlan.ppd\nLet's get back to planning!\n**You**\n550 MB keynote file??\n**Helene**\nHe added fireworks videos"; +"Appstore.Free.Chat.Name" = "**Study Group**"; +"Appstore.Open" = "**Open**\nNo ads, no fees. Open source\ncode free for everyone."; +"Appstore.Powerful" = "**Powerful**\nNo limits on the size of\ngroups and broadcasts."; +"Appstore.Powerful.Chat" = "**James**\nGood morning!\n\nDwayne joined the group\n\n**You**\nDo you have any idea what time it is?\n**Roxanne**\nIs it still morning?\nSure!\nNot sure\n**Emma**\nVoice"; +"Appstore.Private" = "**Private**\nYour data is never disclosed.\nOnly you are in control."; +"Appstore.Private.Chat" = "**You**\nNo limits on the size of your cats."; +"Appstore.Private.Chat.Name" = "**Beatrice**"; +"Appstore.Public" = "**Public**\nPublic channels, open groups,\nbots for integrations."; +"Appstore.Public.Chat1" = "**Financial Times**\nTruth is like the sun. You can shut it out for a time, but it ain’t goin’ away."; +"Appstore.Public.Chat2" = "**Bloomberg**\nWe'll be sending you a few big stories daily, which you can expect to start..."; +"Appstore.Public.Chat3" = "**Health and Safety**\nIf you're looking for official news about the Novel Coronavirus and COVID-19"; +"Appstore.Public.IV" = "We now have enough data to measure the relative effectiveness of major climate solutions. This simulator lets you see which ones would work best.\n\nBloomberg\n\nThe Best Way to Slow Global Warming? You Decide in This Climate Simulator\nIt was on Earth Day 2016 when more than 170 nations signed the Paris Agreement calling for limiting global warming \"to well below 2°C\"."; +"Appstore.Secure" = "**Secure**\nAll chats are protected\nwith strong encryption."; +"Appstore.Secure.Chat" = "**Little Sister**\nAny gift ideas for mom?\n**You**A dog!\n**You**I'm serious. Let's get her a puppy. \n**You**\nI saw this!\n**Little Sister**\nI needed proof this was your idea!"; +"Appstore.Secure.Chat.Name" = "**Little Sister**"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index be930524f7..6b8267c11a 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -289,10 +289,11 @@ public final class NavigateToChatControllerParams { public let animated: Bool public let options: NavigationAnimationOptions public let parentGroupId: PeerGroupId? - public let chatListFilter: ChatListFilterData? + 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: ChatListFilterData? = 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 } } @@ -552,7 +554,7 @@ public protocol SharedAccountContext: AnyObject { var sharedContainerPath: String { get } var basePath: String { get } var mainWindow: Window1? { get } - var accountManager: AccountManager { get } + var accountManager: AccountManager { get } var appLockContext: AppLockContext { get } var currentPresentationData: Atomic { get } @@ -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 @@ -737,6 +740,7 @@ public protocol AccountContext: AnyObject { func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput func chatLocationOutgoingReadState(for location: ChatLocation, contextHolder: Atomic) -> Signal + func chatLocationUnreadCount(for location: ChatLocation, contextHolder: Atomic) -> Signal func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) func scheduleGroupCall(peerId: PeerId) diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index e4f30c083b..8ffe045cfb 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -1,7 +1,8 @@ import Foundation import Display -import Postbox import SwiftSignalKit +import Postbox +import TelegramCore public struct ChatListNodePeersFilter: OptionSet { public var rawValue: Int32 @@ -40,9 +41,9 @@ public final class PeerSelectionControllerParams { public let createNewGroup: (() -> Void)? public let pretendPresentedInModal: Bool public let multipleSelection: Bool - public let forwardedMessagesCount: Int + public let forwardedMessageIds: [EngineMessage.Id] - public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessagesCount: Int = 0) { + public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessageIds: [EngineMessage.Id] = []) { self.context = context self.filter = filter self.hasChatListSelector = hasChatListSelector @@ -53,7 +54,7 @@ public final class PeerSelectionControllerParams { self.createNewGroup = createNewGroup self.pretendPresentedInModal = pretendPresentedInModal self.multipleSelection = multipleSelection - self.forwardedMessagesCount = forwardedMessagesCount + self.forwardedMessageIds = forwardedMessageIds } } @@ -65,7 +66,7 @@ public enum PeerSelectionControllerSendMode { public protocol PeerSelectionController: ViewController { var peerSelected: ((Peer) -> Void)? { get set } - var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode) -> Void)? { get set } + var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode, Bool) -> Void)? { get set } var inProgress: Bool { get set } var customDismiss: (() -> Void)? { get set } } diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 6eedded2b2..83f88f17c8 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -443,7 +443,7 @@ public protocol PresentationGroupCall: AnyObject { func playTone(_ tone: PresentationGroupCallTone) func updateMuteState(peerId: PeerId, isMuted: Bool) -> GroupCallParticipantsContext.Participant.MuteState? - func setShouldBeRecording(_ shouldBeRecording: Bool, title: String?) + func setShouldBeRecording(_ shouldBeRecording: Bool, title: String?, videoOrientation: Bool?) func updateTitle(_ title: String) diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index 2dda65089b..4f186d65d9 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -869,6 +869,7 @@ public final class AnimatedStickerNode: ASDisplayNode { self.timer.swap(nil)?.invalidate() } + private weak var nodeToCopyFrameFrom: AnimatedStickerNode? override public func didLoad() { super.didLoad() @@ -879,8 +880,22 @@ public final class AnimatedStickerNode: ASDisplayNode { //self.renderer = MetalAnimationRenderer() #endif self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) + if let contents = self.nodeToCopyFrameFrom?.renderer?.contents { + self.renderer?.contents = contents + } + self.nodeToCopyFrameFrom = nil self.addSubnode(self.renderer!) } + + public func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) { + if let renderer = self.renderer { + if let contents = otherNode?.renderer?.contents { + renderer.contents = contents + } + } else { + self.nodeToCopyFrameFrom = otherNode + } + } public func setup(source: AnimatedStickerNodeSource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) { if width < 2 || height < 2 { @@ -973,7 +988,7 @@ public final class AnimatedStickerNode: ASDisplayNode { } private var isSetUpForPlayback = false - + public func play(firstFrame: Bool = false, fromIndex: Int? = nil) { switch self.playbackMode { case .once: 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/AppLock/BUILD b/submodules/AppLock/BUILD index ff6e8e8147..a4e95c2e8b 100644 --- a/submodules/AppLock/BUILD +++ b/submodules/AppLock/BUILD @@ -14,6 +14,7 @@ swift_library( "//submodules/Display:Display", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/MonotonicTime:MonotonicTime", "//submodules/PasscodeUI:PasscodeUI", diff --git a/submodules/AppLock/Sources/AppLock.swift b/submodules/AppLock/Sources/AppLock.swift index 6fe5ddef16..51e480825e 100644 --- a/submodules/AppLock/Sources/AppLock.swift +++ b/submodules/AppLock/Sources/AppLock.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import Postbox +import TelegramCore import Display import SwiftSignalKit import MonotonicTime @@ -65,7 +66,7 @@ public final class AppLockContextImpl: AppLockContext { private let syncQueue = Queue() private let applicationBindings: TelegramApplicationBindings - private let accountManager: AccountManager + private let accountManager: AccountManager private let presentationDataSignal: Signal private let window: Window1? private let rootController: UIViewController? @@ -90,7 +91,7 @@ public final class AppLockContextImpl: AppLockContext { private var lastActiveTimestamp: Double? private var lastActiveValue: Bool = false - public init(rootPath: String, window: Window1?, rootController: UIViewController?, applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, presentationDataSignal: Signal, lockIconInitialFrame: @escaping () -> CGRect?) { + public init(rootPath: String, window: Window1?, rootController: UIViewController?, applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, presentationDataSignal: Signal, lockIconInitialFrame: @escaping () -> CGRect?) { assert(Queue.mainQueue().isCurrent()) self.applicationBindings = applicationBindings diff --git a/submodules/AsyncDisplayKit/BUILD b/submodules/AsyncDisplayKit/BUILD index 6d054283b8..c7b86e3485 100644 --- a/submodules/AsyncDisplayKit/BUILD +++ b/submodules/AsyncDisplayKit/BUILD @@ -14,6 +14,9 @@ objc_library( "Source/**/*.m", "Source/**/*.mm", ]) + private_headers, + copts = [ + "-Werror", + ], hdrs = public_headers, defines = [ "MINIMAL_ASDK", diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm index f17f390aff..29fe8db76b 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm @@ -68,6 +68,9 @@ static ASDisplayNodeNonFatalErrorBlock _nonFatalErrorBlock = nil; @end +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" + @implementation ASDisplayNode @dynamic layoutElementType; @@ -386,9 +389,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" if (!(self = [self init])) { return nil; } +#pragma clang diagnostic pop [self setViewBlock:viewBlock]; if (didLoadBlock != nil) { @@ -405,9 +411,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" if (!(self = [self init])) { return nil; } +#pragma clang diagnostic pop [self setLayerBlock:layerBlock]; if (didLoadBlock != nil) { @@ -3816,3 +3825,5 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; } @end + +#pragma clang diagnostic pop diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm index fdd08f0929..984e293e31 100644 --- a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm @@ -687,7 +687,10 @@ } - (bool)isCurrentlyEmoji { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" NSString *value = [[UITextInputMode currentInputMode] primaryLanguage]; +#pragma clang diagnostic pop if ([value isEqualToString:@"emoji"]) { return true; } else { diff --git a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 5ea0b2e6fe..8165b73087 100644 --- a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -133,7 +133,6 @@ AS_CATEGORY_IMPLEMENTABLE * @note This method should not be called directly outside of ASDisplayNode; use -layoutThatFits: or -calculatedLayout instead. */ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize; -- (CGSize)calculateSizeThatFits:(CGSize)contrainedSize; /** * ASDisplayNode's implementation of -layoutThatFits:parentSize: calls this method to resolve the node's size diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm b/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm index c147f5f263..f8e0d9ca04 100644 --- a/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm +++ b/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm @@ -16,10 +16,6 @@ #import #import -#ifndef __STRICT_ANSI__ - #warning "Texture must be compiled with std=c++11 to prevent layout issues. gnu++ is not supported. This is hopefully temporary." -#endif - AS_EXTERN NSRunLoopMode const UITrackingRunLoopMode; NSInteger const ASDefaultTransactionPriority = 0; diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm b/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm index b24e8e9f58..6e0b33541b 100644 --- a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm +++ b/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm @@ -294,9 +294,7 @@ static void CollectAccessibilityElementsForView(UIView *view, NSMutableArray *el if (viewNode == nil) { return @[]; } - if (true || _accessibilityElements == nil) { - _accessibilityElements = [viewNode accessibilityElements]; - } + _accessibilityElements = [viewNode accessibilityElements]; return _accessibilityElements; } diff --git a/submodules/AsyncDisplayKit/Source/_ASPendingState.mm b/submodules/AsyncDisplayKit/Source/_ASPendingState.mm index 5bf75e50d1..39426df1d2 100644 --- a/submodules/AsyncDisplayKit/Source/_ASPendingState.mm +++ b/submodules/AsyncDisplayKit/Source/_ASPendingState.mm @@ -588,8 +588,8 @@ static UIColor *defaultTintColor = nil; _flags.setSemanticContentAttribute = YES; } -- (void)setAccessibilityCustomActions:(NSArray *)accessibilityCustomActions { - self->accessibilityCustomActions = accessibilityCustomActions; +- (void)setAccessibilityCustomActions:(NSArray *)accessibilityCustomActions_ { + self->accessibilityCustomActions = accessibilityCustomActions_; _flags.setAccessibilityCustomActions = YES; } diff --git a/submodules/AudioBlob/Sources/BlobView.swift b/submodules/AudioBlob/Sources/BlobView.swift index 64ade477e5..c1b287fbe5 100644 --- a/submodules/AudioBlob/Sources/BlobView.swift +++ b/submodules/AudioBlob/Sources/BlobView.swift @@ -43,6 +43,11 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco private let maxLevel: CGFloat private var displayLinkAnimator: ConstantDisplayLinkAnimator? + + private let hierarchyTrackingNode: HierarchyTrackingNode + private var isCurrentlyInHierarchy = true + + public var isManuallyInHierarchy: Bool? private var audioLevel: CGFloat = 0 public var presentationAudioLevel: CGFloat = 0 @@ -93,8 +98,15 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco scaleSpeed: 0.2, isCircle: false ) + + var updateInHierarchy: ((Bool) -> Void)? + self.hierarchyTrackingNode = HierarchyTrackingNode({ value in + updateInHierarchy?(value) + }) super.init(frame: frame) + + self.addSubnode(self.hierarchyTrackingNode) self.addSubnode(self.bigBlob) self.addSubnode(self.mediumBlob) @@ -109,6 +121,12 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco strongSelf.mediumBlob.level = strongSelf.presentationAudioLevel strongSelf.bigBlob.level = strongSelf.presentationAudioLevel } + + updateInHierarchy = { [weak self] value in + if let strongSelf = self { + strongSelf.isCurrentlyInHierarchy = value + } + } } required init?(coder: NSCoder) { @@ -120,6 +138,9 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco } public func setColor(_ color: UIColor, animated: Bool) { + if let isManuallyInHierarchy = self.isManuallyInHierarchy, !isManuallyInHierarchy { + return + } smallBlob.setColor(color, animated: animated) mediumBlob.setColor(color.withAlphaComponent(0.3), animated: animated) bigBlob.setColor(color.withAlphaComponent(0.15), animated: animated) @@ -266,6 +287,9 @@ final class BlobNode: ASDisplayNode { ) } } + + private let hierarchyTrackingNode: HierarchyTrackingNode + private var isCurrentlyInHierarchy = true init( pointsCount: Int, @@ -290,12 +314,24 @@ final class BlobNode: ASDisplayNode { let angle = (CGFloat.pi * 2) / CGFloat(pointsCount) self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2 + + var updateInHierarchy: ((Bool) -> Void)? + self.hierarchyTrackingNode = HierarchyTrackingNode({ value in + updateInHierarchy?(value) + }) super.init() - + + self.addSubnode(self.hierarchyTrackingNode) self.layer.addSublayer(self.shapeLayer) self.shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1) + + updateInHierarchy = { [weak self] value in + if let strongSelf = self { + strongSelf.isCurrentlyInHierarchy = value + } + } } required init?(coder: NSCoder) { @@ -305,7 +341,7 @@ final class BlobNode: ASDisplayNode { func setColor(_ color: UIColor, animated: Bool) { let previousColor = self.shapeLayer.fillColor self.shapeLayer.fillColor = color.cgColor - if animated, let previousColor = previousColor { + if animated, let previousColor = previousColor, self.isCurrentlyInHierarchy { self.shapeLayer.animate(from: previousColor, to: color.cgColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3) } } diff --git a/submodules/BuildConfig/BUILD b/submodules/BuildConfig/BUILD index d3f9c56360..10c5c92dbb 100644 --- a/submodules/BuildConfig/BUILD +++ b/submodules/BuildConfig/BUILD @@ -17,6 +17,7 @@ objc_library( "Sources/*.m", ]), copts = [ + "-Werror", "-DAPP_CONFIG_API_ID={}".format(telegram_api_id), "-DAPP_CONFIG_API_HASH=\\\"{}\\\"".format(telegram_api_hash), "-DAPP_CONFIG_APP_CENTER_ID=\\\"{}\\\"".format(telegram_app_center_id), diff --git a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h index 562a63ce27..d735dd74bc 100644 --- a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h +++ b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h @@ -22,8 +22,8 @@ + (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId; - (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken signatureDict:(NSDictionary * _Nullable)signatureDict; -+ (void)getHardwareEncryptionAvailableWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable))completion; -+ (void)encryptApplicationSecret:(NSData * _Nonnull)secret baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable, NSData * _Nullable))completion; -+ (void)decryptApplicationSecret:(NSData * _Nonnull)secret publicKey:(NSData * _Nonnull)publicKey baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable, bool))completion; ++ (void)getHardwareEncryptionAvailableWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^ _Nonnull)(NSData * _Nullable))completion; ++ (void)encryptApplicationSecret:(NSData * _Nonnull)secret baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^ _Nonnull)(NSData * _Nullable, NSData * _Nullable))completion; ++ (void)decryptApplicationSecret:(NSData * _Nonnull)secret publicKey:(NSData * _Nonnull)publicKey baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^ _Nonnull)(NSData * _Nullable, bool))completion; @end diff --git a/submodules/BuildConfig/Sources/BuildConfig.m b/submodules/BuildConfig/Sources/BuildConfig.m index a4a4cffa9e..3dde1749ae 100644 --- a/submodules/BuildConfig/Sources/BuildConfig.m +++ b/submodules/BuildConfig/Sources/BuildConfig.m @@ -207,7 +207,7 @@ API_AVAILABLE(ios(10)) return bundleSeedID; } -+ (NSString * _Nonnull)applicationSecretTag:(bool)isCheckKey { ++ (NSData * _Nullable)applicationSecretTag:(bool)isCheckKey { if (isCheckKey) { return [[telegramApplicationSecretKey stringByAppendingString:@"_check"] dataUsingEncoding:NSUTF8StringEncoding]; } else { @@ -349,7 +349,7 @@ API_AVAILABLE(ios(10)) CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); NSString *filePath = [rootPath stringByAppendingPathComponent:@".tempkey"]; - NSString *encryptedPath = [rootPath stringByAppendingPathComponent:@".tempkeyEncrypted"]; + //NSString *encryptedPath = [rootPath stringByAppendingPathComponent:@".tempkeyEncrypted"]; NSData *currentData = [NSData dataWithContentsOfFile:filePath]; NSData *resultData = nil; diff --git a/submodules/BuildConfigExtra/BUILD b/submodules/BuildConfigExtra/BUILD index 7e3972e6a0..fb403a5b73 100644 --- a/submodules/BuildConfigExtra/BUILD +++ b/submodules/BuildConfigExtra/BUILD @@ -7,6 +7,9 @@ objc_library( "Sources/**/*.m", "Sources/**/*.h", ]), + copts = [ + "-Werror", + ], hdrs = glob([ "PublicHeaders/**/*.h", ]), diff --git a/submodules/BuildConfigExtra/PublicHeaders/BuildConfigExtra/BuildConfigExtra.h b/submodules/BuildConfigExtra/PublicHeaders/BuildConfigExtra/BuildConfigExtra.h index bd4c629fa0..576a2e45e6 100644 --- a/submodules/BuildConfigExtra/PublicHeaders/BuildConfigExtra/BuildConfigExtra.h +++ b/submodules/BuildConfigExtra/PublicHeaders/BuildConfigExtra/BuildConfigExtra.h @@ -2,8 +2,6 @@ @interface BuildConfigExtra : NSObject -- (instancetype _Nonnull)initWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId; - + (NSDictionary * _Nonnull)signatureDict; @end 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/ChatInterfaceState/Sources/ChatInterfaceState.swift b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift index ff8496c42d..f79653a76b 100644 --- a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift +++ b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift @@ -264,6 +264,7 @@ public final class ChatInterfaceState: Codable, Equatable { public let composeDisableUrlPreview: String? public let replyMessageId: EngineMessage.Id? public let forwardMessageIds: [EngineMessage.Id]? + public let forwardMessageHideSendersNames: Bool public let editMessage: ChatEditMessageState? public let selectionState: ChatInterfaceSelectionState? public let messageActionsState: ChatInterfaceMessageActionsState @@ -306,6 +307,7 @@ public final class ChatInterfaceState: Codable, Equatable { self.composeDisableUrlPreview = nil self.replyMessageId = nil self.forwardMessageIds = nil + self.forwardMessageHideSendersNames = false self.editMessage = nil self.selectionState = nil self.messageActionsState = ChatInterfaceMessageActionsState() @@ -315,12 +317,13 @@ public final class ChatInterfaceState: Codable, Equatable { self.inputLanguage = nil } - public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: EngineMessage.Id?, forwardMessageIds: [EngineMessage.Id]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { + public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: EngineMessage.Id?, forwardMessageIds: [EngineMessage.Id]?, forwardMessageHideSendersNames: Bool, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { self.timestamp = timestamp self.composeInputState = composeInputState self.composeDisableUrlPreview = composeDisableUrlPreview self.replyMessageId = replyMessageId self.forwardMessageIds = forwardMessageIds + self.forwardMessageHideSendersNames = forwardMessageHideSendersNames self.editMessage = editMessage self.selectionState = selectionState self.messageActionsState = messageActionsState @@ -357,6 +360,7 @@ public final class ChatInterfaceState: Codable, Equatable { } else { self.forwardMessageIds = nil } + self.forwardMessageHideSendersNames = ((try? container.decodeIfPresent(Int32.self, forKey: "fhn")) ?? 0) != 0 if let editMessage = try? container.decodeIfPresent(ChatEditMessageState.self, forKey: "em") { self.editMessage = editMessage } else { @@ -406,6 +410,7 @@ public final class ChatInterfaceState: Codable, Equatable { } else { try container.encodeNil(forKey: "fm") } + try container.encode((self.forwardMessageHideSendersNames ? 1 : 0) as Int32, forKey: "fhn") if let editMessage = self.editMessage { try container.encode(editMessage, forKey: "em") } else { @@ -446,6 +451,9 @@ public final class ChatInterfaceState: Codable, Equatable { } else if (lhs.forwardMessageIds != nil) != (rhs.forwardMessageIds != nil) { return false } + if lhs.forwardMessageHideSendersNames != rhs.forwardMessageHideSendersNames { + return false + } if lhs.messageActionsState != rhs.messageActionsState { return false } @@ -467,11 +475,11 @@ public final class ChatInterfaceState: Codable, Equatable { public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { let updatedComposeInputState = inputState - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: disableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: disableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { @@ -483,15 +491,19 @@ public final class ChatInterfaceState: Codable, Equatable { updatedComposeInputState = inputState } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedReplyMessageId(_ replyMessageId: EngineMessage.Id?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedForwardMessageIds(_ forwardMessageIds: [EngineMessage.Id]?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + } + + public func withUpdatedForwardMessageHideSendersNames(_ forwardMessageHideSendersNames: Bool) -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedSelectedMessages(_ messageIds: [EngineMessage.Id]) -> ChatInterfaceState { @@ -502,7 +514,7 @@ public final class ChatInterfaceState: Codable, Equatable { for messageId in messageIds { selectedIds.insert(messageId) } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withToggledSelectedMessages(_ messageIds: [EngineMessage.Id], value: Bool) -> ChatInterfaceState { @@ -517,39 +529,39 @@ public final class ChatInterfaceState: Codable, Equatable { selectedIds.remove(messageId) } } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withoutSelectionState() -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedSilentPosting(_ silentPosting: Bool) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: silentPosting, inputLanguage: self.inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedInputLanguage(_ inputLanguage: String?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: inputLanguage) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, forwardMessageHideSendersNames: self.forwardMessageHideSendersNames, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: inputLanguage) } public static func parse(_ state: OpaqueChatInterfaceState) -> ChatInterfaceState { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 1d9cd0209d..5e20fd528e 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -612,7 +612,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController scrollToEndIfExists = true } - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), activateInput: activateInput && !peer.isDeleted, scrollToEndIfExists: scrollToEndIfExists, animated: !scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, chatListFilter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.data, completion: { [weak self] controller in + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), activateInput: activateInput && !peer.isDeleted, scrollToEndIfExists: scrollToEndIfExists, animated: !scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, chatListFilter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id, completion: { [weak self] controller in self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) if let promoInfo = promoInfo { switch promoInfo { diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 5224869b22..3929232285 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -891,12 +891,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }).start() let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true)) - peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode in + peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, hideSendersNames in guard let strongSelf = self, let strongController = peerSelectionController else { return } strongController.dismiss() - + var result: [EnqueueMessage] = [] if messageText.string.count > 0 { let inputText = convertMarkdownToAttributes(messageText) @@ -912,8 +912,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } + var attributes: [MessageAttribute] = [] + if hideSendersNames { + attributes.append(ForwardHideSendersNamesMessageAttribute()) + } + result.append(contentsOf: messageIds.map { messageId -> EnqueueMessage in - return .forward(source: messageId, grouping: .auto, attributes: [], correlationId: nil) + return .forward(source: messageId, grouping: .auto, attributes: attributes, correlationId: nil) }) var displayPeers: [Peer] = [] diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index e9c213c773..7bae52d5e5 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1289,7 +1289,7 @@ public final class ChatListNode: ListView { } } - self.didEndScrolling = { [weak self] in + self.didEndScrolling = { [weak self] _ in guard let strongSelf = self else { return } diff --git a/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift b/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift index ab7ef7a0f3..3c93404932 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift @@ -59,6 +59,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode { text = strings.Activity_PlayingGame case .typingText: text = strings.DialogList_Typing + case .choosingSticker: + text = strings.Activity_ChoosingSticker case .speakingInGroupCall: text = "" } @@ -77,6 +79,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode { state = .playingGame(string, lightColor) case .speakingInGroupCall: state = .typingText(string, lightColor) + case .choosingSticker: + state = .choosingSticker(string, lightColor) } } else { let text: String @@ -99,6 +103,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode { text = strings.DialogList_SinglePlayingGameSuffix(peerTitle).string case .typingText: text = strings.DialogList_SingleTypingSuffix(peerTitle).string + case .choosingSticker: + text = "" case .speakingInGroupCall: text = "" } @@ -120,6 +126,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode { state = .playingGame(string, lightColor) case .speakingInGroupCall: state = .typingText(string, lightColor) + case .choosingSticker: + state = .none } } } else { diff --git a/submodules/ChatTitleActivityNode/Sources/ChatChoosingStickerActivityContentNode.swift b/submodules/ChatTitleActivityNode/Sources/ChatChoosingStickerActivityContentNode.swift new file mode 100644 index 0000000000..ede3b2ab0d --- /dev/null +++ b/submodules/ChatTitleActivityNode/Sources/ChatChoosingStickerActivityContentNode.swift @@ -0,0 +1,145 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import LegacyComponents + +private func interpolate(from: CGFloat, to: CGFloat, value: CGFloat) -> CGFloat { + return (1.0 - value) * from + value * to +} + +private final class ChatChoosingStickerActivityIndicatorNodeParameters: NSObject { + let color: UIColor + let progress: CGFloat + + init(color: UIColor, progress: CGFloat) { + self.color = color + self.progress = progress + } +} + +private class ChatChoosingStickerActivityIndicatorNode: ChatTitleActivityIndicatorNode { + override var duration: CFTimeInterval { + return 2.0 + } + + override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { + if let color = self.color { + return ChatChoosingStickerActivityIndicatorNodeParameters(color: color, progress: self.progress) + } else { + return nil + } + } + + @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { + let context = UIGraphicsGetCurrentContext()! + + if !isRasterizing { + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.fill(bounds) + } + + guard let parameters = parameters as? ChatChoosingStickerActivityIndicatorNodeParameters else { + return + } + + context.setFillColor(UIColor.red.cgColor) +// context.fill(bounds) + + let color = parameters.color + context.setFillColor(color.cgColor) + context.setStrokeColor(color.cgColor) + + var heightProgress: CGFloat = parameters.progress * 4.0 + if heightProgress > 3.0 { + heightProgress = 4.0 - heightProgress + } else if heightProgress > 2.0 { + heightProgress = heightProgress - 2.0 + heightProgress *= heightProgress + } else if heightProgress > 1.0 { + heightProgress = 2.0 - heightProgress + } else { + heightProgress *= heightProgress + } + + var pupilProgress: CGFloat = parameters.progress * 4.0 + if pupilProgress > 2.0 { + pupilProgress = 3.0 - pupilProgress + } + pupilProgress = min(1.0, max(0.0, pupilProgress)) + pupilProgress *= pupilProgress + + var positionProgress: CGFloat = parameters.progress * 2.0 + if positionProgress > 1.0 { + positionProgress = 2.0 - positionProgress + } + + let eyeWidth: CGFloat = 6.0 + let eyeHeight: CGFloat = 11.0 - 2.0 * heightProgress + + let eyeOffset: CGFloat = -1.0 + positionProgress * 2.0 + let leftCenter = CGPoint(x: bounds.width / 2.0 - eyeWidth - 1.0 + eyeOffset, y: bounds.height / 2.0) + let rightCenter = CGPoint(x: bounds.width / 2.0 + 1.0 + eyeOffset, y: bounds.height / 2.0) + + let pupilSize: CGFloat = 4.0 + let pupilCenter = CGPoint(x: -1.0 + pupilProgress * 2.0, y: 0.0) + + context.strokeEllipse(in: CGRect(x: leftCenter.x - eyeWidth / 2.0, y: leftCenter.y - eyeHeight / 2.0, width: eyeWidth, height: eyeHeight)) + context.fillEllipse(in: CGRect(x: leftCenter.x - pupilSize / 2.0 + pupilCenter.x * eyeWidth / 4.0, y: leftCenter.y - pupilSize / 2.0, width: pupilSize, height: pupilSize)) + + context.strokeEllipse(in: CGRect(x: rightCenter.x - eyeWidth / 2.0, y: rightCenter.y - eyeHeight / 2.0, width: eyeWidth, height: eyeHeight)) + context.fillEllipse(in: CGRect(x: rightCenter.x - pupilSize / 2.0 + pupilCenter.x * eyeWidth / 4.0, y: rightCenter.y - pupilSize / 2.0, width: pupilSize, height: pupilSize)) + +// context.strokeEllipse(in: CGRect(x: 0.0, y: 0.0, width: 10.0, height: 20.0)) +// context.fillEllipse(in: CGRect(x: , y: , width: 4.0, height: 4.0)) + + } +} + +class ChatChoosingStickerActivityContentNode: ChatTitleActivityContentNode { + private let indicatorNode: ChatChoosingStickerActivityIndicatorNode + private let advanced: Bool + + init(text: NSAttributedString, color: UIColor) { + self.indicatorNode = ChatChoosingStickerActivityIndicatorNode(color: color) + + var text = text + self.advanced = text.string == "choosing sticker" + if self.advanced { + let mutable = text.mutableCopy() as? NSMutableAttributedString + mutable?.replaceCharacters(in: NSMakeRange(2, 2), with: " ") + if let updated = mutable{ + text = updated + } + } + + super.init(text: text) + + self.addSubnode(self.indicatorNode) + } + + override func updateLayout(_ constrainedSize: CGSize, offset: CGFloat, alignment: NSTextAlignment) -> CGSize { + let size = self.textNode.updateLayout(constrainedSize) + let indicatorSize = CGSize(width: 24.0, height: 16.0) + let originX: CGFloat + let indicatorOriginX: CGFloat + if case .center = alignment { + if self.advanced { + originX = floorToScreenPixels((-size.width) / 2.0) + } else { + originX = floorToScreenPixels((indicatorSize.width - size.width) / 2.0) + } + } else { + originX = indicatorSize.width + } + self.textNode.frame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: size) + if self.advanced { + indicatorOriginX = self.textNode.frame.minX + 14.0 + UIScreenPixel + } else { + indicatorOriginX = self.textNode.frame.minX - indicatorSize.width + } + self.indicatorNode.frame = CGRect(origin: CGPoint(x: indicatorOriginX, y: 0.0), size: indicatorSize) + return CGSize(width: size.width + indicatorSize.width, height: size.height) + } +} diff --git a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.swift b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.swift index d07bd226a4..a2a1abf891 100644 --- a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.swift +++ b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.swift @@ -23,6 +23,7 @@ public enum ChatTitleActivityNodeState: Equatable { case recordingVoice(NSAttributedString, UIColor) case recordingVideo(NSAttributedString, UIColor) case playingGame(NSAttributedString, UIColor) + case choosingSticker(NSAttributedString, UIColor) func contentNode() -> ChatTitleActivityContentNode? { switch self { @@ -40,6 +41,8 @@ public enum ChatTitleActivityNodeState: Equatable { return ChatRecordingVideoActivityContentNode(text: text, color: color) case let .playingGame(text, color): return ChatPlayingActivityContentNode(text: text, color: color) + case let .choosingSticker(text, color): + return ChatChoosingStickerActivityContentNode(text: text, color: color) } } diff --git a/submodules/CloudData/BUILD b/submodules/CloudData/BUILD index 83da0d43aa..874cb8a64f 100644 --- a/submodules/CloudData/BUILD +++ b/submodules/CloudData/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/MtProtoKit:MtProtoKit", "//submodules/EncryptionProvider:EncryptionProvider", ], diff --git a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift index ead71c9b1f..0d64eb3a67 100644 --- a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift +++ b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift @@ -175,6 +175,8 @@ public final class _UpdatedChildComponent { var _removed: Bool = false var _position: CGPoint? var _opacity: CGFloat? + var _cornerRadius: CGFloat? + var _clipsToBounds: Bool? fileprivate var transitionAppear: Transition.Appear? fileprivate var transitionAppearWithGuide: (Transition.AppearWithGuide, _AnyChildComponent.Id)? @@ -241,6 +243,16 @@ public final class _UpdatedChildComponent { return self } + @discardableResult public func cornerRadius(_ cornerRadius: CGFloat) -> _UpdatedChildComponent { + self._cornerRadius = cornerRadius + return self + } + + @discardableResult public func clipsToBounds(_ clipsToBounds: Bool) -> _UpdatedChildComponent { + self._clipsToBounds = clipsToBounds + return self + } + @discardableResult public func gesture(_ gesture: Gesture) -> _UpdatedChildComponent { self.gestures.append(gesture) return self @@ -672,6 +684,8 @@ public extension CombinedComponent { updatedChild.view.frame = updatedChild.size.centered(around: updatedChild._position ?? CGPoint()) updatedChild.view.alpha = updatedChild._opacity ?? 1.0 + updatedChild.view.clipsToBounds = updatedChild._clipsToBounds ?? false + updatedChild.view.layer.cornerRadius = updatedChild._cornerRadius ?? 0.0 updatedChild.view.context(typeErasedComponent: updatedChild.component).erasedState._updated = { [weak viewContext] transition in guard let viewContext = viewContext else { return diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index f0187f4d08..a69a7bf576 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -192,6 +192,28 @@ public struct Transition { } } + public func setSublayerTransform(view: UIView, transform: CATransform3D, completion: ((Bool) -> Void)? = nil) { + switch self.animation { + case .none: + view.layer.sublayerTransform = transform + completion?(true) + case let .curve(duration, curve): + let previousValue = view.layer.sublayerTransform + view.layer.sublayerTransform = transform + view.layer.animate( + from: NSValue(caTransform3D: previousValue), + to: NSValue(caTransform3D: transform), + keyPath: "transform", + duration: duration, + delay: 0.0, + curve: curve, + removeOnCompletion: true, + additive: false, + completion: completion + ) + } + } + public func animateScale(view: UIView, from fromValue: CGFloat, to toValue: CGFloat, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { switch self.animation { case .none: diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index 3a8e3eb8a4..cc5c5a3652 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -11,6 +10,7 @@ import AccountContext import AlertUI import PresentationDataUtils import TextFormat +import Postbox private struct OrderedLinkedListItemOrderingId: RawRepresentable, Hashable { var rawValue: Int @@ -423,7 +423,7 @@ private struct CreatePollControllerState: Equatable { var isEditingSolution: Bool = false } -private func createPollControllerEntries(presentationData: PresentationData, peer: Peer, state: CreatePollControllerState, limitsConfiguration: LimitsConfiguration, defaultIsQuiz: Bool?) -> [CreatePollEntry] { +private func createPollControllerEntries(presentationData: PresentationData, peer: EnginePeer, state: CreatePollControllerState, limitsConfiguration: EngineConfiguration.Limits, defaultIsQuiz: Bool?) -> [CreatePollEntry] { var entries: [CreatePollEntry] = [] var textLimitText = ItemListSectionHeaderAccessoryText(value: "", color: .generic) @@ -453,7 +453,7 @@ private func createPollControllerEntries(presentationData: PresentationData, pee } var canBePublic = true - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { canBePublic = false } @@ -483,7 +483,7 @@ private func createPollControllerEntries(presentationData: PresentationData, pee return entries } -public func createPollController(context: AccountContext, peer: Peer, isQuiz: Bool? = nil, completion: @escaping (EnqueueMessage) -> Void) -> ViewController { +public func createPollController(context: AccountContext, peer: EnginePeer, isQuiz: Bool? = nil, completion: @escaping (EnqueueMessage) -> Void) -> ViewController { var initialState = CreatePollControllerState() if let isQuiz = isQuiz { initialState.isQuiz = isQuiz @@ -742,12 +742,13 @@ public func createPollController(context: AccountContext, peer: Peer, isQuiz: Bo }) let previousOptionIds = Atomic<[Int]?>(value: nil) - - let limitsKey = PostboxViewKey.preferences(keys: Set([PreferencesKeys.limitsConfiguration])) - let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, context.account.postbox.combinedView(keys: [limitsKey])) - |> map { presentationData, state, combinedView -> (ItemListControllerState, (ItemListNodeState, Any)) in - let limitsConfiguration: LimitsConfiguration = (combinedView.views[limitsKey] as? PreferencesView)?.values[PreferencesKeys.limitsConfiguration] as? LimitsConfiguration ?? LimitsConfiguration.defaultValue - + + let signal = combineLatest(queue: .mainQueue(), + context.sharedContext.presentationData, + statePromise.get(), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.Limits()) + ) + |> map { presentationData, state, limitsConfiguration -> (ItemListControllerState, (ItemListNodeState, Any)) in var enabled = true if processPollText(state.text).isEmpty { enabled = false diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index d8a2854c12..63b019afa2 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -1407,7 +1407,7 @@ public final class ContactListNode: ASDisplayNode { } }) - self.listNode.didEndScrolling = { [weak self] in + self.listNode.didEndScrolling = { [weak self] _ in if let strongSelf = self { let _ = strongSelf.contentScrollingEnded?(strongSelf.listNode) } 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/ContactListUI/Sources/InviteContactsController.swift b/submodules/ContactListUI/Sources/InviteContactsController.swift index 45bd3d0b8d..7f52577b8d 100644 --- a/submodules/ContactListUI/Sources/InviteContactsController.swift +++ b/submodules/ContactListUI/Sources/InviteContactsController.swift @@ -178,7 +178,7 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr } } - self.contactsNode.listNode.didEndScrolling = { [weak self] in + self.contactsNode.listNode.didEndScrolling = { [weak self] _ in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { let _ = fixNavigationSearchableListNodeScrolling(strongSelf.contactsNode.listNode, searchNode: searchContentNode) } diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift index a71f96627e..938e641801 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift @@ -69,7 +69,7 @@ private func loadCountryCodes() -> [Country] { private var countryCodes: [Country] = loadCountryCodes() private var countryCodesByPrefix: [String: (Country, Country.CountryCode)] = [:] -public func loadServerCountryCodes(accountManager: AccountManager, engine: TelegramEngineUnauthorized, completion: @escaping () -> Void) { +public func loadServerCountryCodes(accountManager: AccountManager, engine: TelegramEngineUnauthorized, completion: @escaping () -> Void) { let _ = (engine.localization.getCountriesList(accountManager: accountManager, langCode: nil) |> deliverOnMainQueue).start(next: { countries in countryCodes = countries @@ -93,7 +93,7 @@ public func loadServerCountryCodes(accountManager: AccountManager, engine: Teleg }) } -public func loadServerCountryCodes(accountManager: AccountManager, engine: TelegramEngine, completion: @escaping () -> Void) { +public func loadServerCountryCodes(accountManager: AccountManager, engine: TelegramEngine, completion: @escaping () -> Void) { let _ = (engine.localization.getCountriesList(accountManager: accountManager, langCode: nil) |> deliverOnMainQueue).start(next: { countries in countryCodes = countries diff --git a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift index 0c88bf011b..28fb2cf08c 100644 --- a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift @@ -30,7 +30,7 @@ private enum DebugAccountsControllerSection: Int32 { } private enum DebugAccountsControllerEntry: ItemListNodeEntry { - case record(PresentationTheme, AccountRecord, Bool) + case record(PresentationTheme, AccountRecord, Bool) case loginNewAccount(PresentationTheme) var section: ItemListSectionId { @@ -87,7 +87,7 @@ private enum DebugAccountsControllerEntry: ItemListNodeEntry { } } -private func debugAccountsControllerEntries(view: AccountRecordsView, presentationData: PresentationData) -> [DebugAccountsControllerEntry] { +private func debugAccountsControllerEntries(view: AccountRecordsView, presentationData: PresentationData) -> [DebugAccountsControllerEntry] { var entries: [DebugAccountsControllerEntry] = [] for entry in view.records.sorted(by: { @@ -101,7 +101,7 @@ private func debugAccountsControllerEntries(view: AccountRecordsView, presentati return entries } -public func debugAccountsController(context: AccountContext, accountManager: AccountManager) -> ViewController { +public func debugAccountsController(context: AccountContext, accountManager: AccountManager) -> ViewController { var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? let arguments = DebugAccountsControllerArguments(context: context, presentController: { controller, arguments in diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift index 8d3e7a6ae6..83a49888c7 100644 --- a/submodules/Display/Source/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -300,18 +300,23 @@ public func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> U context.scaleBy(x: 1.0, y: -1.0) context.translateBy(x: -imageRect.midX, y: -imageRect.midY) context.clip(to: imageRect, mask: image.cgImage!) - - let gradientColors = colors.map { $0.cgColor } as CFArray - let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) - - var locations: [CGFloat] = [] - for i in 0 ..< colors.count { - locations.append(delta * CGFloat(i)) + + if colors.count >= 2 { + let gradientColors = colors.map { $0.cgColor } as CFArray + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + let t = CGFloat(i) / CGFloat(colors.count - 1) + locations.append(t) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: imageRect.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions()) + } else if !colors.isEmpty { + context.setFillColor(colors[0].cgColor) + context.fill(imageRect) } - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! - - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: imageRect.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions()) context.restoreGState() } diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index f8615f2ebc..b1cd926fca 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -262,6 +262,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture public final var synchronousNodes = false public final var debugInfo = false + public final var useSingleDimensionTouchPoint = false + public var enableExtractedBackgrounds: Bool = false { didSet { if self.enableExtractedBackgrounds != oldValue { @@ -292,12 +294,15 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public final var visibleBottomContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public final var beganInteractiveDragging: (CGPoint) -> Void = { _ in } - public final var endedInteractiveDragging: () -> Void = { } - public final var didEndScrolling: (() -> Void)? + public final var endedInteractiveDragging: (CGPoint) -> Void = { _ in } + public final var didEndScrolling: ((Bool) -> Void)? private var currentGeneralScrollDirection: GeneralScrollDirection? public final var generalScrollDirectionUpdated: (GeneralScrollDirection) -> Void = { _ in } + public private(set) var isReordering = false + public final var willBeginReorder: (CGPoint) -> Void = { _ in } + public final var reorderBegan: () -> Void = { } public final var reorderItem: (Int, Int, Any?) -> Signal = { _, _, _ in return .single(false) } public final var reorderCompleted: (Any?) -> Void = { _ in } @@ -320,8 +325,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture private var reorderNode: ListViewReorderingItemNode? private var reorderFeedback: HapticFeedback? private var reorderFeedbackDisposable: MetaDisposable? - private var isReorderingItems: Bool = false + private var reorderInProgress: Bool = false private var reorderingItemsCompleted: (() -> Void)? + private var reorderScrollStartTimestamp: Double? + public var reorderedItemHasShadow = true private let waitingForNodesDisposable = MetaDisposable() @@ -392,15 +399,19 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let itemNodeFrame = itemNode.frame let itemNodeBounds = itemNode.bounds if itemNode.isReorderable(at: point.offsetBy(dx: -itemNodeFrame.minX + itemNodeBounds.minX, dy: -itemNodeFrame.minY + itemNodeBounds.minY)) { - strongSelf.beginReordering(itemNode: itemNode) - return true + let requiresLongPress = !strongSelf.reorderedItemHasShadow + return (true, requiresLongPress, itemNode) } break } } } } - return false + return (false, false, nil) + }, willBegin: { [weak self] point in + self?.willBeginReorder(point) + }, began: { [weak self] itemNode in + self?.beginReordering(itemNode: itemNode) }, ended: { [weak self] in self?.endReordering() }, moved: { [weak self] offset in @@ -467,10 +478,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } private func beginReordering(itemNode: ListViewItemNode) { + self.isReordering = true + self.reorderBegan() + if let reorderNode = self.reorderNode { reorderNode.removeFromSupernode() } - let reorderNode = ListViewReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin) + let reorderNode = ListViewReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin, hasShadow: self.reorderedItemHasShadow) self.reorderNode = reorderNode if let verticalScrollIndicator = self.verticalScrollIndicator { self.insertSubnode(reorderNode, belowSubnode: verticalScrollIndicator) @@ -507,8 +521,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } strongSelf.reorderCompleted(strongSelf.opaqueTransactionState) + strongSelf.isReordering = false } - if self.isReorderingItems { + if self.reorderInProgress { self.reorderingItemsCompleted = f } else { f() @@ -585,14 +600,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if self.reorderFeedbackDisposable == nil { self.reorderFeedbackDisposable = MetaDisposable() } - self.isReorderingItems = true + self.reorderInProgress = true self.reorderFeedbackDisposable?.set((self.reorderItem(reorderItemIndex, toIndex, self.opaqueTransactionState) |> deliverOnMainQueue).start(next: { [weak self] value in guard let strongSelf = self else { return } - strongSelf.isReorderingItems = false + strongSelf.reorderInProgress = false if let reorderingItemsCompleted = strongSelf.reorderingItemsCompleted { strongSelf.reorderingItemsCompleted = nil reorderingItemsCompleted() @@ -710,9 +725,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.resetScrollIndicatorFlashTimer(start: true) self.lastContentOffsetTimestamp = 0.0 - self.didEndScrolling?() + self.didEndScrolling?(false) } - self.endedInteractiveDragging() + self.endedInteractiveDragging(self.touchesPosition) } public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { @@ -722,7 +737,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.updateHeaderItemsFlashing(animated: true) self.resetScrollIndicatorFlashTimer(start: true) if !scrollView.isTracking { - self.didEndScrolling?() + self.didEndScrolling?(true) } } @@ -3135,16 +3150,38 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture reverseAnimation = reverseBasicAnimation } } - animation.completion = { _ in - for itemNode in temporaryPreviousNodes { - itemNode.removeFromSupernode() - itemNode.extractedBackgroundNode?.removeFromSupernode() - } - for headerNode in temporaryHeaderNodes { - headerNode.removeFromSupernode() + + if scrollToItem.displayLink { + self.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, -offset, 0.0) + let offsetAnimation = ListViewAnimation(from: -offset, to: 0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem, beginAt: timestamp, update: { [weak self] progress, currentValue in + if let strongSelf = self { + strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, currentValue, 0.0) + + if progress == 1.0 { + for itemNode in temporaryPreviousNodes { + itemNode.removeFromSupernode() + itemNode.extractedBackgroundNode?.removeFromSupernode() + } + for headerNode in temporaryHeaderNodes { + headerNode.removeFromSupernode() + } + } + } + }) + self.animations.append(offsetAnimation) + } else { + animation.completion = { _ in + for itemNode in temporaryPreviousNodes { + itemNode.removeFromSupernode() + itemNode.extractedBackgroundNode?.removeFromSupernode() + } + for headerNode in temporaryHeaderNodes { + headerNode.removeFromSupernode() + } } + self.layer.add(animation, forKey: nil) } - self.layer.add(animation, forKey: nil) + for itemNode in self.itemNodes { itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -offset), animationCurve: animationCurve, duration: animationDuration) } @@ -3926,7 +3963,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture animation.applyAt(timestamp) if animation.completeAt(timestamp) { - animations.remove(at: i) + self.animations.remove(at: i) animationCount -= 1 i -= 1 } else { @@ -3938,20 +3975,38 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var offsetRanges = OffsetRanges() + var scrollingForReorder = false if let reorderOffset = self.reorderNode?.currentOffset(), !self.itemNodes.isEmpty { let effectiveInsets = self.visualInsets ?? self.insets + + var offset: CGFloat = 6.0 + if let reorderScrollStartTimestamp = self.reorderScrollStartTimestamp, reorderScrollStartTimestamp + 2.0 < timestamp { + offset *= 2.0 + } if reorderOffset < effectiveInsets.top + 10.0 { if self.itemNodes[0].apparentFrame.minY < effectiveInsets.top { continueAnimations = true - offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: 6.0) + offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: offset) + scrollingForReorder = true } } else if reorderOffset > self.visibleSize.height - effectiveInsets.bottom - 10.0 { if self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY > self.visibleSize.height - effectiveInsets.bottom { continueAnimations = true - offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: -6.0) + if self.reorderScrollStartTimestamp == nil { + self.reorderScrollStartTimestamp = timestamp + } + offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: -offset) + scrollingForReorder = true } } } + if scrollingForReorder { + if self.reorderScrollStartTimestamp == nil { + self.reorderScrollStartTimestamp = timestamp + } + } else { + self.reorderScrollStartTimestamp = nil + } var requestUpdateVisibleItems = false var index = 0 @@ -4148,6 +4203,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } public func itemIndexAtPoint(_ point: CGPoint) -> Int? { + var point = point + if self.useSingleDimensionTouchPoint { + point.x = 0.0 + } for itemNode in self.itemNodes { if itemNode.apparentContentFrame.contains(point) { return itemNode.index diff --git a/submodules/Display/Source/ListViewIntermediateState.swift b/submodules/Display/Source/ListViewIntermediateState.swift index 56d0e73bec..096f485fc3 100644 --- a/submodules/Display/Source/ListViewIntermediateState.swift +++ b/submodules/Display/Source/ListViewIntermediateState.swift @@ -31,13 +31,15 @@ public struct ListViewScrollToItem { public let animated: Bool public let curve: ListViewAnimationCurve public let directionHint: ListViewScrollToItemDirectionHint + public let displayLink: Bool - public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint) { + public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint, displayLink: Bool = false) { self.index = index self.position = position self.animated = animated self.curve = curve self.directionHint = directionHint + self.displayLink = displayLink } } diff --git a/submodules/Display/Source/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift index 1edd8073a2..d23236a0ab 100644 --- a/submodules/Display/Source/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -384,7 +384,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { animation.applyAt(timestamp) if animation.completeAt(timestamp) { - animations.remove(at: i) + self.animations.remove(at: i) animationCount -= 1 i -= 1 } else { diff --git a/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift b/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift index 22748518c5..3ebc781735 100644 --- a/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift +++ b/submodules/Display/Source/ListViewReorderingGestureRecognizer.swift @@ -1,28 +1,101 @@ import Foundation import UIKit +import SwiftSignalKit -final class ListViewReorderingGestureRecognizer: UIGestureRecognizer { - private let shouldBegin: (CGPoint) -> Bool +public final class ListViewReorderingGestureRecognizer: UIGestureRecognizer { + private let shouldBegin: (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: ListViewItemNode?) + private let willBegin: (CGPoint) -> Void + private let began: (ListViewItemNode) -> Void private let ended: () -> Void private let moved: (CGFloat) -> Void private var initialLocation: CGPoint? + private var longTapTimer: SwiftSignalKit.Timer? + private var longPressTimer: SwiftSignalKit.Timer? - init(shouldBegin: @escaping (CGPoint) -> Bool, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { + private var itemNode: ListViewItemNode? + + public init(shouldBegin: @escaping (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: ListViewItemNode?), willBegin: @escaping (CGPoint) -> Void, began: @escaping (ListViewItemNode) -> Void, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { self.shouldBegin = shouldBegin + self.willBegin = willBegin + self.began = began self.ended = ended self.moved = moved super.init(target: nil, action: nil) } - override func reset() { + deinit { + self.longTapTimer?.invalidate() + self.longPressTimer?.invalidate() + } + + private func startLongTapTimer() { + self.longTapTimer?.invalidate() + let longTapTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self] in + self?.longTapTimerFired() + }, queue: Queue.mainQueue()) + self.longTapTimer = longTapTimer + longTapTimer.start() + } + + private func stopLongTapTimer() { + self.itemNode = nil + self.longTapTimer?.invalidate() + self.longTapTimer = nil + } + + private func startLongPressTimer() { + self.longPressTimer?.invalidate() + let longPressTimer = SwiftSignalKit.Timer(timeout: 0.8, repeat: false, completion: { [weak self] in + self?.longPressTimerFired() + }, queue: Queue.mainQueue()) + self.longPressTimer = longPressTimer + longPressTimer.start() + } + + private func stopLongPressTimer() { + self.itemNode = nil + self.longPressTimer?.invalidate() + self.longPressTimer = nil + } + + override public func reset() { super.reset() + self.itemNode = nil + self.stopLongTapTimer() + self.stopLongPressTimer() self.initialLocation = nil } - override func touchesBegan(_ touches: Set, with event: UIEvent) { + private func longTapTimerFired() { + guard let location = self.initialLocation else { + return + } + + self.longTapTimer?.invalidate() + self.longTapTimer = nil + + self.willBegin(location) + } + + private func longPressTimerFired() { + guard let _ = self.initialLocation else { + return + } + + self.state = .began + self.longPressTimer?.invalidate() + self.longPressTimer = nil + self.longTapTimer?.invalidate() + self.longTapTimer = nil + if let itemNode = self.itemNode { + self.began(itemNode) + } + } + + override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) if self.numberOfTouches > 1 { @@ -32,40 +105,79 @@ final class ListViewReorderingGestureRecognizer: UIGestureRecognizer { } if self.state == .possible { - if let location = touches.first?.location(in: self.view), self.shouldBegin(location) { - self.initialLocation = location - self.state = .began + if let location = touches.first?.location(in: self.view) { + let (allowed, requiresLongPress, itemNode) = self.shouldBegin(location) + if allowed { + self.itemNode = itemNode + self.initialLocation = location + if requiresLongPress { + self.startLongTapTimer() + self.startLongPressTimer() + } else { + self.state = .began + if let itemNode = self.itemNode { + self.began(itemNode) + } + } + } else { + self.state = .failed + } } else { self.state = .failed } } } - override func touchesEnded(_ touches: Set, with event: UIEvent) { + override public func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) + self.initialLocation = nil + + self.stopLongTapTimer() + if self.longPressTimer != nil { + self.stopLongPressTimer() + self.state = .failed + } if self.state == .began || self.state == .changed { self.ended() self.state = .failed } } - override func touchesCancelled(_ touches: Set, with event: UIEvent) { + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { super.touchesCancelled(touches, with: event) + self.initialLocation = nil + + self.stopLongTapTimer() + if self.longPressTimer != nil { + self.stopLongPressTimer() + self.state = .failed + } if self.state == .began || self.state == .changed { self.ended() self.state = .failed } } - override func touchesMoved(_ touches: Set, with event: UIEvent) { + override public func touchesMoved(_ touches: Set, with event: UIEvent) { super.touchesMoved(touches, with: event) if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) { self.state = .changed let offset = location.y - initialLocation.y self.moved(offset) + } else if let touch = touches.first, let initialTapLocation = self.initialLocation, self.longPressTimer != nil { + let touchLocation = touch.location(in: self.view) + let dX = touchLocation.x - initialTapLocation.x + let dY = touchLocation.y - initialTapLocation.y + + if dX * dX + dY * dY > 3.0 * 3.0 { + self.stopLongTapTimer() + self.stopLongPressTimer() + self.initialLocation = nil + self.state = .failed + } } } } diff --git a/submodules/Display/Source/ListViewReorderingItemNode.swift b/submodules/Display/Source/ListViewReorderingItemNode.swift index 8d72a524ba..b1dd58b192 100644 --- a/submodules/Display/Source/ListViewReorderingItemNode.swift +++ b/submodules/Display/Source/ListViewReorderingItemNode.swift @@ -25,14 +25,16 @@ private final class CopyView: UIView { let topShadow: UIImageView let bottomShadow: UIImageView - override init(frame: CGRect) { + init(frame: CGRect, hasShadow: Bool) { self.topShadow = UIImageView() self.bottomShadow = UIImageView() super.init(frame: frame) - self.topShadow.image = generateShadowImage(mirror: true) - self.bottomShadow.image = generateShadowImage(mirror: false) + if hasShadow { + self.topShadow.image = generateShadowImage(mirror: true) + self.bottomShadow.image = generateShadowImage(mirror: false) + } self.addSubview(self.topShadow) self.addSubview(self.bottomShadow) @@ -51,9 +53,9 @@ final class ListViewReorderingItemNode: ASDisplayNode { private let copyView: CopyView private let initialLocation: CGPoint - init(itemNode: ListViewItemNode, initialLocation: CGPoint) { + init(itemNode: ListViewItemNode, initialLocation: CGPoint, hasShadow: Bool) { self.itemNode = itemNode - self.copyView = CopyView(frame: CGRect()) + self.copyView = CopyView(frame: CGRect(), hasShadow: hasShadow) let snapshotView = itemNode.snapshotForReordering() self.initialLocation = initialLocation @@ -68,8 +70,6 @@ final class ListViewReorderingItemNode: ASDisplayNode { self.copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: itemNode.bounds.size) self.copyView.topShadow.frame = CGRect(origin: CGPoint(x: 0.0, y: -30.0), size: CGSize(width: copyView.bounds.size.width, height: 45.0)) - - self.copyView.bottomShadow.image = generateShadowImage(mirror: false) self.copyView.bottomShadow.frame = CGRect(origin: CGPoint(x: 0.0, y: self.copyView.bounds.size.height - 15.0), size: CGSize(width: self.copyView.bounds.size.width, height: 45.0)) self.copyView.topShadow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) 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/GalleryUI/Sources/GalleryControllerNode.swift b/submodules/GalleryUI/Sources/GalleryControllerNode.swift index 1ec92c83e3..9243fb7c4a 100644 --- a/submodules/GalleryUI/Sources/GalleryControllerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryControllerNode.swift @@ -258,13 +258,11 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture transition.updateFrame(node: navigationBar, frame: CGRect(origin: CGPoint(x: 0.0, y: self.areControlsHidden ? -navigationBarHeight : 0.0), size: CGSize(width: layout.size.width, height: navigationBarHeight))) } - let displayThumbnailPanel = layout.size.width < layout.size.height var thumbnailPanelHeight: CGFloat = 0.0 if let currentThumbnailContainerNode = self.currentThumbnailContainerNode { let panelHeight: CGFloat = 52.0 - if displayThumbnailPanel { - thumbnailPanelHeight = 52.0 - } + thumbnailPanelHeight = panelHeight + let thumbnailsFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 40.0 - panelHeight + 4.0 - layout.intrinsicInsets.bottom + (self.areControlsHidden ? 106.0 : 0.0)), size: CGSize(width: layout.size.width, height: panelHeight - 4.0)) transition.updateFrame(node: currentThumbnailContainerNode, frame: thumbnailsFrame) currentThumbnailContainerNode.updateLayout(size: thumbnailsFrame.size, transition: transition) diff --git a/submodules/GalleryUI/Sources/GalleryFooterNode.swift b/submodules/GalleryUI/Sources/GalleryFooterNode.swift index 4f14a43e08..8ec0a40ffb 100644 --- a/submodules/GalleryUI/Sources/GalleryFooterNode.swift +++ b/submodules/GalleryUI/Sources/GalleryFooterNode.swift @@ -70,7 +70,10 @@ public final class GalleryFooterNode: ASDisplayNode { } } - let effectiveThumbnailPanelHeight = self.currentThumbnailPanelHeight ?? thumbnailPanelHeight + var effectiveThumbnailPanelHeight = self.currentThumbnailPanelHeight ?? thumbnailPanelHeight + if layout.size.width > layout.size.height { + effectiveThumbnailPanelHeight = 0.0 + } var backgroundHeight: CGFloat = 0.0 let verticalOffset: CGFloat = isHidden ? (layout.size.width > layout.size.height ? 44.0 : (effectiveThumbnailPanelHeight > 0.0 ? 106.0 : 54.0)) : 0.0 if let footerContentNode = self.currentFooterContentNode { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 56578535cd..d66f332707 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -19,6 +19,7 @@ import ContextUI import SaveToCameraRoll import UndoUI import TelegramUIPreferences +import OpenInExternalAppUI public enum UniversalVideoGalleryItemContentInfo { case message(Message) @@ -2088,8 +2089,38 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { c.setItems(strongSelf.contextMenuSpeedItems()) }))) + + if let (message, _, _) = strongSelf.contentInfo() { + for media in message.media { + if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + let url = content.url + + let item = OpenInItem.url(url: url) + let canOpenIn = availableOpenInOptions(context: strongSelf.context, item: item).count > 1 + let openText = canOpenIn ? strongSelf.presentationData.strings.Conversation_FileOpenIn : strongSelf.presentationData.strings.Conversation_LinkDialogOpen + items.append(.action(ContextMenuActionItem(text: openText, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + if let strongSelf = self, let controller = strongSelf.galleryController() { + var presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + if !presentationData.theme.overallDarkAppearance { + presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) + } + let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in + if let strongSelf = self { + strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {}) + } + }) + controller.present(actionSheet, in: .window(.root)) + } + }))) + break + } + } + } + if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveToGallery, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveVideo, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.default) if let strongSelf = self { diff --git a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift index 21fcde858a..4b4eb1ae2c 100644 --- a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift +++ b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift @@ -232,7 +232,7 @@ public final class GradientBackgroundNode: ASDisplayNode { if let current = self._dimmedImage { return current } else if let (size, colors, positions) = self.dimmedImageParams { - self._dimmedImage = generateGradient(size: size, colors: colors, positions: positions, adjustSaturation: 1.7) + self._dimmedImage = generateGradient(size: size, colors: colors, positions: positions, adjustSaturation: self.saturation) return self._dimmedImage } else { return nil @@ -245,8 +245,11 @@ public final class GradientBackgroundNode: ASDisplayNode { private let useSharedAnimationPhase: Bool static var sharedPhase: Int = 0 - public init(colors: [UIColor]? = nil, useSharedAnimationPhase: Bool = false) { + private let saturation: CGFloat + + public init(colors: [UIColor]? = nil, useSharedAnimationPhase: Bool = false, adjustSaturation: Bool = true) { self.useSharedAnimationPhase = useSharedAnimationPhase + self.saturation = adjustSaturation ? 1.7 : 1.0 self.contentView = UIImageView() let defaultColors: [UIColor] = [ UIColor(rgb: 0x7FA381), @@ -345,7 +348,7 @@ public final class GradientBackgroundNode: ASDisplayNode { images.append(generateGradient(size: imageSize, colors: self.colors, positions: morphedPositions)) if needDimmedImages { - dimmedImages.append(generateGradient(size: imageSize, colors: self.colors, positions: morphedPositions, adjustSaturation: 1.7)) + dimmedImages.append(generateGradient(size: imageSize, colors: self.colors, positions: morphedPositions, adjustSaturation: self.saturation)) } } @@ -393,7 +396,7 @@ public final class GradientBackgroundNode: ASDisplayNode { let image = generateGradient(size: imageSize, colors: self.colors, positions: positions) self.contentView.image = image - let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: 1.7) + let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: self.saturation) self._dimmedImage = dimmedImage self.dimmedImageParams = (imageSize, self.colors, positions) @@ -406,7 +409,7 @@ public final class GradientBackgroundNode: ASDisplayNode { let image = generateGradient(size: imageSize, colors: self.colors, positions: positions) self.contentView.image = image - let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: 1.7) + let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: self.saturation) self.dimmedImageParams = (imageSize, self.colors, positions) for cloneNode in self.cloneNodes { diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 685cfd3e9d..50b71f819d 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/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 2b090b5ecc..ed023b003b 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -121,7 +121,7 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem { public enum StickerPackThumbnailItem: Equatable { case still(TelegramMediaImageRepresentation) - case animated(MediaResource) + case animated(MediaResource, PixelDimensions) public static func ==(lhs: StickerPackThumbnailItem, rhs: StickerPackThumbnailItem) -> Bool { switch lhs { @@ -131,8 +131,8 @@ public enum StickerPackThumbnailItem: Equatable { } else { return false } - case let .animated(lhsResource): - if case let .animated(rhsResource) = rhs, lhsResource.isEqual(to: rhsResource) { + case let .animated(lhsResource, lhsDimensions): + if case let .animated(rhsResource, rhsDimensions) = rhs, lhsResource.isEqual(to: rhsResource), lhsDimensions == rhsDimensions { return true } else { return false @@ -439,7 +439,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { var resourceReference: MediaResourceReference? if let thumbnail = item.packInfo.thumbnail { if item.packInfo.flags.contains(.isAnimated) { - thumbnailItem = .animated(thumbnail.resource) + thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions) resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: item.packInfo.id.id, accessHash: item.packInfo.accessHash), resource: thumbnail.resource) } else { thumbnailItem = .still(thumbnail) @@ -447,7 +447,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } } else if let item = item.topItem { if item.file.isAnimatedSticker { - thumbnailItem = .animated(item.file.resource) + thumbnailItem = .animated(item.file.resource, item.file.dimensions ?? PixelDimensions(width: 100, height: 100)) resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource) } else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil)) @@ -474,7 +474,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stillImageSize, boundingSize: stillImageSize, intrinsicInsets: UIEdgeInsets())) updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.account.postbox, resource: representation.resource, nilIfEmpty: true) } - case let .animated(resource): + case let .animated(resource, _): imageSize = imageBoundingSize if fileUpdated { @@ -706,10 +706,12 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let boundingSize = CGSize(width: 34.0, height: 34.0) if let thumbnailItem = thumbnailItem, let imageSize = imageSize { let imageFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: floor((layout.contentSize.height - imageSize.height) / 2.0)), size: imageSize) + var thumbnailDimensions = PixelDimensions(width: 512, height: 512) switch thumbnailItem { - case .still: + case let .still(representation): transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame) - case let .animated(resource): + thumbnailDimensions = representation.dimensions + case let .animated(resource, _): transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame) let animationNode: AnimatedStickerNode @@ -733,7 +735,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { if let placeholderNode = strongSelf.placeholderNode { placeholderNode.frame = imageFrame - placeholderNode.update(backgroundColor: nil, foregroundColor: item.presentationData.theme.list.disclosureArrowColor.blitOver(item.presentationData.theme.list.itemBlocksBackgroundColor, alpha: 0.55), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.packInfo.immediateThumbnailData, size: imageFrame.size, imageSize: CGSize(width: 100.0, height: 100.0)) + placeholderNode.update(backgroundColor: nil, foregroundColor: item.presentationData.theme.list.disclosureArrowColor.blitOver(item.presentationData.theme.list.itemBlocksBackgroundColor, alpha: 0.55), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.packInfo.immediateThumbnailData, size: imageFrame.size, imageSize: thumbnailDimensions.cgSize) } } diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 31aa4eda42..47dfeb4141 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -352,7 +352,7 @@ open class ItemListControllerNode: ASDisplayNode { } } - self.listNode.didEndScrolling = { [weak self] in + self.listNode.didEndScrolling = { [weak self] _ in if let strongSelf = self { let _ = strongSelf.contentScrollingEnded?(strongSelf.listNode) } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCameraView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCameraView.h index 5d2fa718d3..91ce61daab 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCameraView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCameraView.h @@ -22,6 +22,7 @@ - (void)setZoomedProgress:(CGFloat)progress; +- (void)saveStartImage:(void (^)(void))completion; - (TGCameraPreviewView *)previewView; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h index 80103834ca..2d83e9e92d 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h @@ -61,6 +61,7 @@ - (instancetype)initWithContext:(id)context camera:(bool)hasCamera selfPortrait:(bool)selfPortrait forProfilePhoto:(bool)forProfilePhoto assetType:(TGMediaAssetType)assetType saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping allowSelection:(bool)allowSelection allowEditing:(bool)allowEditing document:(bool)document selectionLimit:(int)selectionLimit; +- (void)saveStartImage; - (UIView *)getItemSnapshot:(NSString *)uniqueId; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h index c08deb5ed3..1841ea47ba 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h @@ -77,5 +77,6 @@ typedef enum { + (UIInterfaceOrientation)_interfaceOrientationForDeviceOrientation:(UIDeviceOrientation)orientation; + (UIImage *)startImage; ++ (void)generateStartImageWithImage:(UIImage *)frameImage; @end diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m b/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m index 2c0c95f510..6256244d6d 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m @@ -247,4 +247,15 @@ _iconView.frame = CGRectMake((self.frame.size.width - _iconView.frame.size.width) / 2, (self.frame.size.height - _iconView.frame.size.height) / 2, _iconView.frame.size.width, _iconView.frame.size.height); } +- (void)saveStartImage:(void (^)(void))completion { + [_camera captureNextFrameCompletion:^(UIImage *frameImage) { + [[SQueue concurrentDefaultQueue] dispatch:^{ + [TGCameraController generateStartImageWithImage:frameImage]; + TGDispatchOnMainThread(^{ + completion(); + }); + }]; + }]; +} + @end diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m index 420fe63797..66d38b04d1 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m @@ -107,6 +107,8 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; bool _saveEditedPhotos; TGMenuSheetPallete *_pallete; + + bool _savingStartImage; } @end @@ -347,6 +349,15 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; [_itemsSizeChangedDisposable dispose]; } +- (void)saveStartImage { + _savingStartImage = true; + __weak TGAttachmentCameraView *weakCameraView = _cameraView; + [_cameraView saveStartImage:^{ + __strong TGAttachmentCameraView *strongCameraView = weakCameraView; + [strongCameraView stopPreview]; + }]; +} + - (UIView *)getItemSnapshot:(NSString *)uniqueId { for (UIView *cell in _collectionView.visibleCells) { if ([cell isKindOfClass:[TGAttachmentAssetCell class]]) { @@ -1227,7 +1238,9 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; { [super menuView:menuView didDisappearAnimated:animated]; menuView.tapDismissalAllowed = nil; - [_cameraView stopPreview]; + if (!_savingStartImage) { + [_cameraView stopPreview]; + } } #pragma mark - diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index 31b075faef..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") @@ -182,7 +182,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati carouselItem.stickersContext = paintStickersContext carouselItem.suggestionContext = legacySuggestionContext(context: context, peerId: peer.id, chatLocation: chatLocation) carouselItem.recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + var openedCamera = false + controller.willDismiss = { [weak carouselItem] _ in + if let carouselItem = carouselItem, !openedCamera { + carouselItem.saveStartImage() + } + } carouselItem.cameraPressed = { [weak controller, weak parentController] cameraView in + openedCamera = true if let controller = controller { if let parentController = parentController, parentController.context.currentlyInSplitView() { return @@ -311,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/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h index 9f687b8774..415f4f974f 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h @@ -56,6 +56,8 @@ + (MTQueue * _Nonnull)contextQueue; ++ (void)performWithObjCTry:(dispatch_block_t _Nonnull)block; + - (instancetype _Nonnull)initWithSerialization:(id _Nonnull)serialization encryptionProvider:(id _Nonnull)encryptionProvider apiEnvironment:(MTApiEnvironment * _Nonnull)apiEnvironment isTestingEnvironment:(bool)isTestingEnvironment useTempAuthKeys:(bool)useTempAuthKeys; - (void)performBatchUpdates:(void (^ _Nonnull)())block; diff --git a/submodules/MtProtoKit/Sources/MTContext.m b/submodules/MtProtoKit/Sources/MTContext.m index 8216fe97b7..7810572886 100644 --- a/submodules/MtProtoKit/Sources/MTContext.m +++ b/submodules/MtProtoKit/Sources/MTContext.m @@ -283,6 +283,13 @@ static int32_t fixedTimeDifferenceValue = 0; return queue; } ++ (void)performWithObjCTry:(dispatch_block_t _Nonnull)block { + @try { + block(); + } @finally { + } +} + - (void)cleanup { NSDictionary *datacenterAuthActions = _datacenterAuthActions; diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift b/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift index 1c34056f43..339bdc1437 100644 --- a/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift +++ b/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift @@ -62,9 +62,20 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope var options: [OpenInOption] = [] switch item { case let .url(url): - options.append(OpenInOption(identifier: "safari", application: .safari, action: { - return .openUrl(url: url) - })) + var skipSafari = false + if url.contains("youtube.com/") || url.contains("youtu.be/") { + let updatedUrl = url.replacingOccurrences(of: "https://", with: "youtube://").replacingOccurrences(of: "http://", with: "youtube://") + options.append(OpenInOption(identifier: "youtube", application: .other(title: "YouTube", identifier: 544007664, scheme: "youtube", store: nil), action: { + return .openUrl(url: updatedUrl) + })) + skipSafari = true + } + + if !skipSafari { + options.append(OpenInOption(identifier: "safari", application: .safari, action: { + return .openUrl(url: url) + })) + } options.append(OpenInOption(identifier: "chrome", application: .other(title: "Chrome", identifier: 535886823, scheme: "googlechrome", store: nil), action: { if let url = URL(string: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true) { diff --git a/submodules/OpenSSLEncryptionProvider/BUILD b/submodules/OpenSSLEncryptionProvider/BUILD index 5c3324325d..648b6de676 100644 --- a/submodules/OpenSSLEncryptionProvider/BUILD +++ b/submodules/OpenSSLEncryptionProvider/BUILD @@ -15,6 +15,7 @@ objc_library( ], copts = [ "-DTELEGRAM_USE_BORINGSSL=1", + "-Werror", ], deps = [ "//submodules/EncryptionProvider:EncryptionProvider", diff --git a/submodules/OpenSSLEncryptionProvider/Sources/OpenSSLEncryptionProvider.m b/submodules/OpenSSLEncryptionProvider/Sources/OpenSSLEncryptionProvider.m index 6493d03cca..55954c8636 100644 --- a/submodules/OpenSSLEncryptionProvider/Sources/OpenSSLEncryptionProvider.m +++ b/submodules/OpenSSLEncryptionProvider/Sources/OpenSSLEncryptionProvider.m @@ -90,70 +90,70 @@ NS_ASSUME_NONNULL_BEGIN - (id)clone:(id)other { assert([other isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *otherImpl = other; + MTBignumImpl *otherImpl = (MTBignumImpl *)other; return [otherImpl copy]; } - (void)setConstantTime:(id)other { assert([other isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *otherImpl = other; #ifndef TELEGRAM_USE_BORINGSSL + MTBignumImpl *otherImpl = (MTBignumImpl *)other; BN_set_flags(otherImpl->_value, BN_FLG_CONSTTIME); #endif } - (void)assignWordTo:(id)bignum value:(unsigned long)value { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; BN_set_word(bignumImpl->_value, value); } - (void)assignHexTo:(id)bignum value:(NSString *)value { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; BN_hex2bn(&bignumImpl->_value, [value UTF8String]); } - (void)assignBinTo:(id)bignum value:(NSData *)value { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; BN_bin2bn(value.bytes, value.length, bignumImpl->_value); } - (void)assignOneTo:(id)bignum { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; BN_one(bignumImpl->_value); } - (void)assignZeroTo:(id)bignum { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; BN_zero(bignumImpl->_value); } - (bool)isOne:(id)bignum { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; return BN_is_one(bignumImpl->_value); } - (bool)isZero:(id)bignum { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; return BN_is_zero(bignumImpl->_value); } - (NSData *)getBin:(id)bignum { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; int numBytes = BN_num_bytes(bignumImpl->_value); NSMutableData *data = [[NSMutableData alloc] initWithLength:numBytes]; @@ -164,7 +164,7 @@ NS_ASSUME_NONNULL_BEGIN - (int)isPrime:(id)bignum numberOfChecks:(int)numberOfChecks { assert([bignum isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *bignumImpl = bignum; + MTBignumImpl *bignumImpl = (MTBignumImpl *)bignum; return BN_is_prime_ex(bignumImpl->_value, numberOfChecks, _context, NULL); } @@ -172,8 +172,8 @@ NS_ASSUME_NONNULL_BEGIN - (int)compare:(id)a with:(id)b { assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; return BN_cmp(aImpl->_value, bImpl->_value); } @@ -183,10 +183,10 @@ NS_ASSUME_NONNULL_BEGIN assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); assert([mod isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; - MTBignumImpl *modImpl = mod; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; + MTBignumImpl *modImpl = (MTBignumImpl *)mod; return BN_mod_add(resultImpl->_value, aImpl->_value, bImpl->_value, modImpl->_value, _context) != 0; } @@ -196,10 +196,10 @@ NS_ASSUME_NONNULL_BEGIN assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); assert([mod isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; - MTBignumImpl *modImpl = mod; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; + MTBignumImpl *modImpl = (MTBignumImpl *)mod; return BN_mod_sub(resultImpl->_value, aImpl->_value, bImpl->_value, modImpl->_value, _context) != 0; } @@ -209,10 +209,10 @@ NS_ASSUME_NONNULL_BEGIN assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); assert([mod isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; - MTBignumImpl *modImpl = mod; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; + MTBignumImpl *modImpl = (MTBignumImpl *)mod; return BN_mod_mul(resultImpl->_value, aImpl->_value, bImpl->_value, modImpl->_value, _context) != 0; } @@ -222,10 +222,10 @@ NS_ASSUME_NONNULL_BEGIN assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); assert([mod isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; - MTBignumImpl *modImpl = mod; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; + MTBignumImpl *modImpl = (MTBignumImpl *)mod; return BN_mod_exp(resultImpl->_value, aImpl->_value, bImpl->_value, modImpl->_value, _context) != 0; } @@ -234,9 +234,9 @@ NS_ASSUME_NONNULL_BEGIN assert([result isKindOfClass:[MTBignumImpl class]]); assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; return BN_add(resultImpl->_value, aImpl->_value, bImpl->_value) != 0; } @@ -245,9 +245,9 @@ NS_ASSUME_NONNULL_BEGIN assert([result isKindOfClass:[MTBignumImpl class]]); assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; return BN_sub(resultImpl->_value, aImpl->_value, bImpl->_value) != 0; } @@ -256,9 +256,9 @@ NS_ASSUME_NONNULL_BEGIN assert([result isKindOfClass:[MTBignumImpl class]]); assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; return BN_mul(resultImpl->_value, aImpl->_value, bImpl->_value, _context) != 0; } @@ -267,9 +267,9 @@ NS_ASSUME_NONNULL_BEGIN assert([result isKindOfClass:[MTBignumImpl class]]); assert([a isKindOfClass:[MTBignumImpl class]]); assert([b isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *bImpl = b; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *bImpl = (MTBignumImpl *)b; return BN_exp(resultImpl->_value, aImpl->_value, bImpl->_value, _context) != 0; } @@ -278,16 +278,16 @@ NS_ASSUME_NONNULL_BEGIN assert([result isKindOfClass:[MTBignumImpl class]]); assert([a isKindOfClass:[MTBignumImpl class]]); assert([mod isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; - MTBignumImpl *modImpl = mod; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; + MTBignumImpl *modImpl = (MTBignumImpl *)mod; return BN_mod_inverse(resultImpl->_value, aImpl->_value, modImpl->_value, _context) != 0; } - (unsigned long)modWord:(id)a mod:(unsigned long)mod { assert([a isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *aImpl = a; + MTBignumImpl *aImpl = (MTBignumImpl *)a; return BN_mod_word(aImpl->_value, mod); } @@ -295,8 +295,8 @@ NS_ASSUME_NONNULL_BEGIN - (bool)rightShift1Bit:(id)result a:(id)a { assert([result isKindOfClass:[MTBignumImpl class]]); assert([a isKindOfClass:[MTBignumImpl class]]); - MTBignumImpl *resultImpl = result; - MTBignumImpl *aImpl = a; + MTBignumImpl *resultImpl = (MTBignumImpl *)result; + MTBignumImpl *aImpl = (MTBignumImpl *)a; return BN_rshift1(resultImpl->_value, aImpl->_value); } @@ -328,10 +328,10 @@ NS_ASSUME_NONNULL_BEGIN MTBignumContextImpl *context = [[MTBignumContextImpl alloc] init]; - MTBignumImpl *a = [context create]; + MTBignumImpl *a = (MTBignumImpl *)[context create]; [context assignBinTo:a value:data]; - MTBignumImpl *r = [context create]; + MTBignumImpl *r = (MTBignumImpl *)[context create]; [context modExpInto:r a:a b:[context rsaGetE:rsaKey] mod:[context rsaGetN:rsaKey]]; return [context getBin:r]; @@ -388,7 +388,7 @@ NS_ASSUME_NONNULL_BEGIN BN_mod_exp(r, a, RSA_get0_e(rsaKey), RSA_get0_n(rsaKey), ctx); unsigned char *res = malloc((size_t)BN_num_bytes(r)); - int resLen = BN_bn2bin(r, res); + int resLen = (int)(BN_bn2bin(r, res)); BN_CTX_free(ctx); BN_free(a); diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryController.swift b/submodules/PasscodeUI/Sources/PasscodeEntryController.swift index 54ceb4e794..38734d0a56 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryController.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryController.swift @@ -4,6 +4,7 @@ import Display import AsyncDisplayKit import SwiftSignalKit import Postbox +import TelegramCore import TelegramPresentationData import TelegramUIPreferences import AccountContext @@ -37,7 +38,7 @@ public final class PasscodeEntryController: ViewController { } private let applicationBindings: TelegramApplicationBindings - private let accountManager: AccountManager + private let accountManager: AccountManager private let appLockContext: AppLockContext private let presentationDataSignal: Signal @@ -61,7 +62,7 @@ public final class PasscodeEntryController: ViewController { private var statusBarHost: StatusBarHost? private var previousStatusBarStyle: UIStatusBarStyle? - public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, appLockContext: AppLockContext, presentationData: PresentationData, presentationDataSignal: Signal, statusBarHost: StatusBarHost?, challengeData: PostboxAccessChallengeData, biometrics: PasscodeEntryControllerBiometricsMode, arguments: PasscodeEntryControllerPresentationArguments) { + public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, appLockContext: AppLockContext, presentationData: PresentationData, presentationDataSignal: Signal, statusBarHost: StatusBarHost?, challengeData: PostboxAccessChallengeData, biometrics: PasscodeEntryControllerBiometricsMode, arguments: PasscodeEntryControllerPresentationArguments) { self.applicationBindings = applicationBindings self.accountManager = accountManager self.appLockContext = appLockContext diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift index 3d99089a0a..43f382b3cc 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift @@ -18,7 +18,7 @@ private let subtitleFont = Font.regular(15.0) private let buttonFont = Font.regular(17.0) final class PasscodeEntryControllerNode: ASDisplayNode { - private let accountManager: AccountManager + private let accountManager: AccountManager private var presentationData: PresentationData private var theme: PresentationTheme private var strings: PresentationStrings @@ -53,7 +53,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode { var checkPasscode: ((String) -> Void)? var requestBiometrics: (() -> Void)? - init(accountManager: AccountManager, presentationData: PresentationData, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, passcodeType: PasscodeEntryFieldType, biometricsType: LocalAuthBiometricAuthentication?, arguments: PasscodeEntryControllerPresentationArguments, modalPresentation: Bool) { + init(accountManager: AccountManager, presentationData: PresentationData, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, passcodeType: PasscodeEntryFieldType, biometricsType: LocalAuthBiometricAuthentication?, arguments: PasscodeEntryControllerPresentationArguments, modalPresentation: Bool) { self.accountManager = accountManager self.presentationData = presentationData self.theme = theme @@ -207,7 +207,7 @@ final class PasscodeEntryControllerNode: ASDisplayNode { let baseColor: UIColor let lightness = color.lightness if lightness < 0.1 || lightness > 0.9 { - baseColor = self.theme.chat.message.outgoing.bubble.withoutWallpaper.fill + baseColor = self.theme.chat.message.outgoing.bubble.withoutWallpaper.fill[0] } else{ baseColor = color } 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/ChannelMembersSearchController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift index 7bbdc8eda3..fe90b99711 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift @@ -110,7 +110,7 @@ public final class ChannelMembersSearchController: ViewController { } } - self.controllerNode.listNode.didEndScrolling = { [weak self] in + self.controllerNode.listNode.didEndScrolling = { [weak self] _ in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { let _ = fixNavigationSearchableListNodeScrolling(strongSelf.controllerNode.listNode, searchNode: searchContentNode) } 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/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 5b51dff0a7..ad94da6eb9 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -560,9 +560,17 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa text = presentationData.strings.Channel_Username_InvalidStartsWithNumber } case .startsWithUnderscore: - text = presentationData.strings.Channel_Username_InvalidCharacters + if isGroup { + text = presentationData.strings.Group_Username_InvalidStartsWithUnderscore + } else { + text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore + } case .endsWithUnderscore: - text = presentationData.strings.Channel_Username_InvalidCharacters + if isGroup { + text = presentationData.strings.Group_Username_InvalidEndsWithUnderscore + } else { + text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore + } case .tooShort: if isGroup { text = presentationData.strings.Group_Username_InvalidTooShort @@ -696,9 +704,9 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa case .startsWithDigit: text = presentationData.strings.Group_Username_InvalidStartsWithNumber case .startsWithUnderscore: - text = presentationData.strings.Channel_Username_InvalidCharacters + text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore case .endsWithUnderscore: - text = presentationData.strings.Channel_Username_InvalidCharacters + text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore case .tooShort: text = presentationData.strings.Group_Username_InvalidTooShort case .invalidCharacters: 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/Postbox/Sources/AccountManager.swift b/submodules/Postbox/Sources/AccountManager.swift index 1025cbe4cb..b2a26e9d54 100644 --- a/submodules/Postbox/Sources/AccountManager.swift +++ b/submodules/Postbox/Sources/AccountManager.swift @@ -1,15 +1,19 @@ import Foundation import SwiftSignalKit -public struct AccountManagerModifier { - public let getRecords: () -> [AccountRecord] - public let updateRecord: (AccountRecordId, (AccountRecord?) -> (AccountRecord?)) -> Void - public let getCurrent: () -> (AccountRecordId, [AccountRecordAttribute])? +public protocol AccountManagerTypes { + associatedtype Attribute: AccountRecordAttribute +} + +public struct AccountManagerModifier { + public let getRecords: () -> [AccountRecord] + public let updateRecord: (AccountRecordId, (AccountRecord?) -> (AccountRecord?)) -> Void + public let getCurrent: () -> (AccountRecordId, [Types.Attribute])? public let setCurrentId: (AccountRecordId) -> Void - public let getCurrentAuth: () -> AuthAccountRecord? - public let createAuth: ([AccountRecordAttribute]) -> AuthAccountRecord? + public let getCurrentAuth: () -> AuthAccountRecord? + public let createAuth: ([Types.Attribute]) -> AuthAccountRecord? public let removeAuth: () -> Void - public let createRecord: ([AccountRecordAttribute]) -> AccountRecordId + public let createRecord: ([Types.Attribute]) -> AccountRecordId public let getSharedData: (ValueBoxKey) -> PreferencesEntry? public let updateSharedData: (ValueBoxKey, (PreferencesEntry?) -> PreferencesEntry?) -> Void public let getAccessChallengeData: () -> PostboxAccessChallengeData @@ -21,7 +25,7 @@ public struct AccountManagerModifier { public let clearNotices: () -> Void } -final class AccountManagerImpl { +final class AccountManagerImpl { private let queue: Queue private let basePath: String private let atomicStatePath: String @@ -31,33 +35,33 @@ final class AccountManagerImpl { private var tables: [Table] = [] - private var currentAtomicState: AccountManagerAtomicState + private var currentAtomicState: AccountManagerAtomicState private var currentAtomicStateUpdated = false - private let legacyMetadataTable: AccountManagerMetadataTable - private let legacyRecordTable: AccountManagerRecordTable + private let legacyMetadataTable: AccountManagerMetadataTable + private let legacyRecordTable: AccountManagerRecordTable let sharedDataTable: AccountManagerSharedDataTable let noticeTable: NoticeTable - private var currentRecordOperations: [AccountManagerRecordOperation] = [] - private var currentMetadataOperations: [AccountManagerMetadataOperation] = [] + private var currentRecordOperations: [AccountManagerRecordOperation] = [] + private var currentMetadataOperations: [AccountManagerMetadataOperation] = [] private var currentUpdatedSharedDataKeys = Set() private var currentUpdatedNoticeEntryKeys = Set() private var currentUpdatedAccessChallengeData: PostboxAccessChallengeData? - private var recordsViews = Bag<(MutableAccountRecordsView, ValuePipe)>() + private var recordsViews = Bag<(MutableAccountRecordsView, ValuePipe>)>() - private var sharedDataViews = Bag<(MutableAccountSharedDataView, ValuePipe)>() - private var noticeEntryViews = Bag<(MutableNoticeEntryView, ValuePipe)>() + private var sharedDataViews = Bag<(MutableAccountSharedDataView, ValuePipe>)>() + private var noticeEntryViews = Bag<(MutableNoticeEntryView, ValuePipe>)>() private var accessChallengeDataViews = Bag<(MutableAccessChallengeDataView, ValuePipe)>() - static func getCurrentRecords(basePath: String) -> (records: [AccountRecord], currentId: AccountRecordId?) { + static func getCurrentRecords(basePath: String) -> (records: [AccountRecord], currentId: AccountRecordId?) { let atomicStatePath = "\(basePath)/atomic-state" do { let data = try Data(contentsOf: URL(fileURLWithPath: atomicStatePath)) - let atomicState = try JSONDecoder().decode(AccountManagerAtomicState.self, from: data) + let atomicState = try JSONDecoder().decode(AccountManagerAtomicState.self, from: data) return (atomicState.records.sorted(by: { $0.key.int64 < $1.key.int64 }).map({ $1 }), atomicState.currentRecordId) } catch let e { postboxLog("decode atomic state error: \(e)") @@ -82,15 +86,15 @@ final class AccountManagerImpl { } self.valueBox = valueBox - self.legacyMetadataTable = AccountManagerMetadataTable(valueBox: self.valueBox, table: AccountManagerMetadataTable.tableSpec(0)) - self.legacyRecordTable = AccountManagerRecordTable(valueBox: self.valueBox, table: AccountManagerRecordTable.tableSpec(1)) + self.legacyMetadataTable = AccountManagerMetadataTable(valueBox: self.valueBox, table: AccountManagerMetadataTable.tableSpec(0)) + self.legacyRecordTable = AccountManagerRecordTable(valueBox: self.valueBox, table: AccountManagerRecordTable.tableSpec(1)) self.sharedDataTable = AccountManagerSharedDataTable(valueBox: self.valueBox, table: AccountManagerSharedDataTable.tableSpec(2)) self.noticeTable = NoticeTable(valueBox: self.valueBox, table: NoticeTable.tableSpec(3)) do { let data = try Data(contentsOf: URL(fileURLWithPath: self.atomicStatePath)) do { - let atomicState = try JSONDecoder().decode(AccountManagerAtomicState.self, from: data) + let atomicState = try JSONDecoder().decode(AccountManagerAtomicState.self, from: data) self.currentAtomicState = atomicState } catch let e { postboxLog("decode atomic state error: \(e)") @@ -99,7 +103,7 @@ final class AccountManagerImpl { } } catch let e { postboxLog("load atomic state error: \(e)") - var legacyRecordDict: [AccountRecordId: AccountRecord] = [:] + var legacyRecordDict: [AccountRecordId: AccountRecord] = [:] for record in self.legacyRecordTable.getRecords() { legacyRecordDict[record.id] = record } @@ -131,12 +135,12 @@ final class AccountManagerImpl { assert(self.queue.isCurrent()) } - fileprivate func transaction(ignoreDisabled: Bool, _ f: @escaping (AccountManagerModifier) -> T) -> Signal { + fileprivate func transaction(ignoreDisabled: Bool, _ f: @escaping (AccountManagerModifier) -> T) -> Signal { return Signal { subscriber in self.queue.justDispatch { self.valueBox.begin() - let transaction = AccountManagerModifier(getRecords: { + let transaction = AccountManagerModifier(getRecords: { return self.currentAtomicState.records.map { $0.1 } }, updateRecord: { id, update in let current = self.currentAtomicState.records[id] @@ -167,7 +171,7 @@ final class AccountManagerImpl { return nil } }, createAuth: { attributes in - let record = AuthAccountRecord(id: generateAccountRecordId(), attributes: attributes) + let record = AuthAccountRecord(id: generateAccountRecordId(), attributes: attributes) self.currentAtomicState.currentAuthRecord = record self.currentAtomicStateUpdated = true self.currentMetadataOperations.append(.updateCurrentAuthAccountRecord(record)) @@ -178,7 +182,7 @@ final class AccountManagerImpl { self.currentAtomicStateUpdated = true }, createRecord: { attributes in let id = generateAccountRecordId() - let record = AccountRecord(id: id, attributes: attributes, temporarySessionId: nil) + let record = AccountRecord(id: id, attributes: attributes, temporarySessionId: nil) self.currentAtomicState.records[id] = record self.currentRecordOperations.append(.set(id: id, record: record)) self.currentAtomicStateUpdated = true @@ -241,7 +245,7 @@ final class AccountManagerImpl { if !self.currentRecordOperations.isEmpty || !self.currentMetadataOperations.isEmpty { for (view, pipe) in self.recordsViews.copyItems() { if view.replay(operations: self.currentRecordOperations, metadataOperations: self.currentMetadataOperations) { - pipe.putNext(AccountRecordsView(view)) + pipe.putNext(AccountRecordsView(view)) } } } @@ -249,7 +253,7 @@ final class AccountManagerImpl { if !self.currentUpdatedSharedDataKeys.isEmpty { for (view, pipe) in self.sharedDataViews.copyItems() { if view.replay(accountManagerImpl: self, updatedKeys: self.currentUpdatedSharedDataKeys) { - pipe.putNext(AccountSharedDataView(view)) + pipe.putNext(AccountSharedDataView(view)) } } } @@ -282,22 +286,22 @@ final class AccountManagerImpl { } } - fileprivate func accountRecords() -> Signal { - return self.transaction(ignoreDisabled: false, { transaction -> Signal in + fileprivate func accountRecords() -> Signal, NoError> { + return self.transaction(ignoreDisabled: false, { transaction -> Signal, NoError> in return self.accountRecordsInternal(transaction: transaction) }) |> switchToLatest } - fileprivate func sharedData(keys: Set) -> Signal { - return self.transaction(ignoreDisabled: false, { transaction -> Signal in + fileprivate func sharedData(keys: Set) -> Signal, NoError> { + return self.transaction(ignoreDisabled: false, { transaction -> Signal, NoError> in return self.sharedDataInternal(transaction: transaction, keys: keys) }) |> switchToLatest } - fileprivate func noticeEntry(key: NoticeEntryKey) -> Signal { - return self.transaction(ignoreDisabled: false, { transaction -> Signal in + fileprivate func noticeEntry(key: NoticeEntryKey) -> Signal, NoError> { + return self.transaction(ignoreDisabled: false, { transaction -> Signal, NoError> in return self.noticeEntryInternal(transaction: transaction, key: key) }) |> switchToLatest @@ -310,18 +314,18 @@ final class AccountManagerImpl { |> switchToLatest } - private func accountRecordsInternal(transaction: AccountManagerModifier) -> Signal { + private func accountRecordsInternal(transaction: AccountManagerModifier) -> Signal, NoError> { assert(self.queue.isCurrent()) - let mutableView = MutableAccountRecordsView(getRecords: { + let mutableView = MutableAccountRecordsView(getRecords: { return self.currentAtomicState.records.map { $0.1 } }, currentId: self.currentAtomicState.currentRecordId, currentAuth: self.currentAtomicState.currentAuthRecord) - let pipe = ValuePipe() + let pipe = ValuePipe>() let index = self.recordsViews.add((mutableView, pipe)) let queue = self.queue - return (.single(AccountRecordsView(mutableView)) + return (.single(AccountRecordsView(mutableView)) |> then(pipe.signal())) - |> `catch` { _ -> Signal in + |> `catch` { _ -> Signal, NoError> in } |> afterDisposed { [weak self] in queue.async { @@ -332,15 +336,15 @@ final class AccountManagerImpl { } } - private func sharedDataInternal(transaction: AccountManagerModifier, keys: Set) -> Signal { - let mutableView = MutableAccountSharedDataView(accountManagerImpl: self, keys: keys) - let pipe = ValuePipe() + private func sharedDataInternal(transaction: AccountManagerModifier, keys: Set) -> Signal, NoError> { + let mutableView = MutableAccountSharedDataView(accountManagerImpl: self, keys: keys) + let pipe = ValuePipe>() let index = self.sharedDataViews.add((mutableView, pipe)) let queue = self.queue - return (.single(AccountSharedDataView(mutableView)) + return (.single(AccountSharedDataView(mutableView)) |> then(pipe.signal())) - |> `catch` { _ -> Signal in + |> `catch` { _ -> Signal, NoError> in } |> afterDisposed { [weak self] in queue.async { @@ -351,15 +355,15 @@ final class AccountManagerImpl { } } - private func noticeEntryInternal(transaction: AccountManagerModifier, key: NoticeEntryKey) -> Signal { - let mutableView = MutableNoticeEntryView(accountManagerImpl: self, key: key) - let pipe = ValuePipe() + private func noticeEntryInternal(transaction: AccountManagerModifier, key: NoticeEntryKey) -> Signal, NoError> { + let mutableView = MutableNoticeEntryView(accountManagerImpl: self, key: key) + let pipe = ValuePipe>() let index = self.noticeEntryViews.add((mutableView, pipe)) let queue = self.queue return (.single(NoticeEntryView(mutableView)) |> then(pipe.signal())) - |> `catch` { _ -> Signal in + |> `catch` { _ -> Signal, NoError> in } |> afterDisposed { [weak self] in queue.async { @@ -370,7 +374,7 @@ final class AccountManagerImpl { } } - private func accessChallengeDataInternal(transaction: AccountManagerModifier) -> Signal { + private func accessChallengeDataInternal(transaction: AccountManagerModifier) -> Signal { let mutableView = MutableAccessChallengeDataView(data: transaction.getAccessChallengeData()) let pipe = ValuePipe() let index = self.accessChallengeDataViews.add((mutableView, pipe)) @@ -389,8 +393,8 @@ final class AccountManagerImpl { } } - fileprivate func currentAccountRecord(allocateIfNotExists: Bool) -> Signal<(AccountRecordId, [AccountRecordAttribute])?, NoError> { - return self.transaction(ignoreDisabled: false, { transaction -> Signal<(AccountRecordId, [AccountRecordAttribute])?, NoError> in + fileprivate func currentAccountRecord(allocateIfNotExists: Bool) -> Signal<(AccountRecordId, [Types.Attribute])?, NoError> { + return self.transaction(ignoreDisabled: false, { transaction -> Signal<(AccountRecordId, [Types.Attribute])?, NoError> in let current = transaction.getCurrent() if let _ = current { } else if allocateIfNotExists { @@ -404,7 +408,7 @@ final class AccountManagerImpl { } let signal = self.accountRecordsInternal(transaction: transaction) - |> map { view -> (AccountRecordId, [AccountRecordAttribute])? in + |> map { view -> (AccountRecordId, [Types.Attribute])? in if let currentRecord = view.currentRecord { return (currentRecord.id, currentRecord.attributes) } else { @@ -457,15 +461,15 @@ final class AccountManagerImpl { private let sharedQueue = Queue() -public final class AccountManager { +public final class AccountManager { public let basePath: String public let mediaBox: MediaBox private let queue: Queue - private let impl: QueueLocalObject + private let impl: QueueLocalObject> public let temporarySessionId: Int64 - public static func getCurrentRecords(basePath: String) -> (records: [AccountRecord], currentId: AccountRecordId?) { - return AccountManagerImpl.getCurrentRecords(basePath: basePath) + public static func getCurrentRecords(basePath: String) -> (records: [AccountRecord], currentId: AccountRecordId?) { + return AccountManagerImpl.getCurrentRecords(basePath: basePath) } public init(basePath: String, isTemporary: Bool, isReadOnly: Bool) { @@ -476,7 +480,7 @@ public final class AccountManager { self.temporarySessionId = temporarySessionId let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - if let value = AccountManagerImpl(queue: queue, basePath: basePath, isTemporary: isTemporary, isReadOnly: isReadOnly, temporarySessionId: temporarySessionId) { + if let value = AccountManagerImpl(queue: queue, basePath: basePath, isTemporary: isTemporary, isReadOnly: isReadOnly, temporarySessionId: temporarySessionId) { return value } else { preconditionFailure() @@ -485,7 +489,7 @@ public final class AccountManager { self.mediaBox = MediaBox(basePath: basePath + "/media") } - public func transaction(ignoreDisabled: Bool = false, _ f: @escaping (AccountManagerModifier) -> T) -> Signal { + public func transaction(ignoreDisabled: Bool = false, _ f: @escaping (AccountManagerModifier) -> T) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in @@ -499,7 +503,7 @@ public final class AccountManager { } } - public func accountRecords() -> Signal { + public func accountRecords() -> Signal, NoError> { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in @@ -513,7 +517,7 @@ public final class AccountManager { } } - public func sharedData(keys: Set) -> Signal { + public func sharedData(keys: Set) -> Signal, NoError> { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in @@ -527,7 +531,7 @@ public final class AccountManager { } } - public func noticeEntry(key: NoticeEntryKey) -> Signal { + public func noticeEntry(key: NoticeEntryKey) -> Signal, NoError> { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in @@ -555,7 +559,7 @@ public final class AccountManager { } } - public func currentAccountRecord(allocateIfNotExists: Bool) -> Signal<(AccountRecordId, [AccountRecordAttribute])?, NoError> { + public func currentAccountRecord(allocateIfNotExists: Bool) -> Signal<(AccountRecordId, [Types.Attribute])?, NoError> { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in diff --git a/submodules/Postbox/Sources/AccountManagerAtomicState.swift b/submodules/Postbox/Sources/AccountManagerAtomicState.swift index d74557c755..360464c0d9 100644 --- a/submodules/Postbox/Sources/AccountManagerAtomicState.swift +++ b/submodules/Postbox/Sources/AccountManagerAtomicState.swift @@ -1,6 +1,6 @@ import Foundation -final class AccountManagerAtomicState: Codable { +final class AccountManagerAtomicState: Codable { enum CodingKeys: String, CodingKey { case records case currentRecordId @@ -8,12 +8,12 @@ final class AccountManagerAtomicState: Codable { case accessChallengeData } - var records: [AccountRecordId: AccountRecord] + var records: [AccountRecordId: AccountRecord] var currentRecordId: AccountRecordId? - var currentAuthRecord: AuthAccountRecord? + var currentAuthRecord: AuthAccountRecord? var accessChallengeData: PostboxAccessChallengeData - init(records: [AccountRecordId: AccountRecord] = [:], currentRecordId: AccountRecordId? = nil, currentAuthRecord: AuthAccountRecord? = nil, accessChallengeData: PostboxAccessChallengeData = .none) { + init(records: [AccountRecordId: AccountRecord] = [:], currentRecordId: AccountRecordId? = nil, currentAuthRecord: AuthAccountRecord? = nil, accessChallengeData: PostboxAccessChallengeData = .none) { self.records = records self.currentRecordId = currentRecordId self.currentAuthRecord = currentAuthRecord @@ -22,21 +22,21 @@ final class AccountManagerAtomicState: Codable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - if let records = try? container.decode(Array.self, forKey: .records) { - var recordDict: [AccountRecordId: AccountRecord] = [:] + if let records = try? container.decode(Array>.self, forKey: .records) { + var recordDict: [AccountRecordId: AccountRecord] = [:] for record in records { recordDict[record.id] = record } self.records = recordDict } else { - self.records = try container.decode(Dictionary.self, forKey: .records) + self.records = try container.decode(Dictionary>.self, forKey: .records) } if let idString = try? container.decodeIfPresent(String.self, forKey: .currentRecordId), let idValue = Int64(idString) { self.currentRecordId = AccountRecordId(rawValue: idValue) } else { self.currentRecordId = try container.decodeIfPresent(AccountRecordId.self, forKey: .currentRecordId) } - self.currentAuthRecord = try container.decodeIfPresent(AuthAccountRecord.self, forKey: .currentAuthRecord) + self.currentAuthRecord = try container.decodeIfPresent(AuthAccountRecord.self, forKey: .currentAuthRecord) if let accessChallengeData = try? container.decodeIfPresent(PostboxAccessChallengeData.self, forKey: .accessChallengeData) { self.accessChallengeData = accessChallengeData diff --git a/submodules/Postbox/Sources/AccountManagerMetadataTable.swift b/submodules/Postbox/Sources/AccountManagerMetadataTable.swift index 56407516f3..591a7fb95b 100644 --- a/submodules/Postbox/Sources/AccountManagerMetadataTable.swift +++ b/submodules/Postbox/Sources/AccountManagerMetadataTable.swift @@ -92,16 +92,16 @@ public enum PostboxAccessChallengeData: PostboxCoding, Equatable, Codable { } } -public struct AuthAccountRecord: PostboxCoding, Codable { +public struct AuthAccountRecord: Codable { enum CodingKeys: String, CodingKey { case id case attributes } public let id: AccountRecordId - public let attributes: [AccountRecordAttribute] + public let attributes: [Attribute] - init(id: AccountRecordId, attributes: [AccountRecordAttribute]) { + init(id: AccountRecordId, attributes: [Attribute]) { self.id = id self.attributes = attributes } @@ -109,41 +109,31 @@ public struct AuthAccountRecord: PostboxCoding, Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(AccountRecordId.self, forKey: .id) - let attributesData = try container.decode(Array.self, forKey: .attributes) - var attributes: [AccountRecordAttribute] = [] - for data in attributesData { - if let object = PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? AccountRecordAttribute { - attributes.append(object) + + if let attributesData = try? container.decode(Array.self, forKey: .attributes) { + var attributes: [Attribute] = [] + for data in attributesData { + if let attribute = try? AdaptedPostboxDecoder().decode(Attribute.self, from: data) { + attributes.append(attribute) + } } + self.attributes = attributes + } else { + let attributes = try container.decode([Attribute].self, forKey: .attributes) + self.attributes = attributes } - self.attributes = attributes } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.id, forKey: .id) - let attributesData: [Data] = self.attributes.map { attribute in - let encoder = PostboxEncoder() - encoder.encodeRootObject(attribute) - return encoder.makeData() - } - try container.encode(attributesData, forKey: .attributes) - } - - public init(decoder: PostboxDecoder) { - self.id = AccountRecordId(rawValue: decoder.decodeOptionalInt64ForKey("id")!) - self.attributes = decoder.decodeObjectArrayForKey("attributes").compactMap({ $0 as? AccountRecordAttribute }) - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt64(self.id.rawValue, forKey: "id") - encoder.encodeGenericObjectArray(self.attributes.map { $0 as PostboxCoding }, forKey: "attributes") + try container.encode(self.attributes, forKey: .attributes) } } -enum AccountManagerMetadataOperation { +enum AccountManagerMetadataOperation { case updateCurrentAccountId(AccountRecordId) - case updateCurrentAuthAccountRecord(AuthAccountRecord?) + case updateCurrentAuthAccountRecord(AuthAccountRecord?) } private enum MetadataKey: Int64 { @@ -153,7 +143,7 @@ private enum MetadataKey: Int64 { case version = 3 } -final class AccountManagerMetadataTable: Table { +final class AccountManagerMetadataTable: Table { static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .int64, compactValuesOnCreation: false) } @@ -189,27 +179,26 @@ final class AccountManagerMetadataTable: Table { } } - func setCurrentAccountId(_ id: AccountRecordId, operations: inout [AccountManagerMetadataOperation]) { + func setCurrentAccountId(_ id: AccountRecordId, operations: inout [AccountManagerMetadataOperation]) { var rawValue = id.rawValue self.valueBox.set(self.table, key: self.key(.currentAccountId), value: MemoryBuffer(memory: &rawValue, capacity: 8, length: 8, freeWhenDone: false)) operations.append(.updateCurrentAccountId(id)) } - func getCurrentAuthAccount() -> AuthAccountRecord? { - if let value = self.valueBox.get(self.table, key: self.key(.currentAuthAccount)), let object = PostboxDecoder(buffer: value).decodeRootObject() as? AuthAccountRecord { + func getCurrentAuthAccount() -> AuthAccountRecord? { + if let value = self.valueBox.get(self.table, key: self.key(.currentAuthAccount)) { + let object = try? AdaptedPostboxDecoder().decode(AuthAccountRecord.self, from: value.makeData()) + return object } else { return nil } } - func setCurrentAuthAccount(_ record: AuthAccountRecord?, operations: inout [AccountManagerMetadataOperation]) { + func setCurrentAuthAccount(_ record: AuthAccountRecord?, operations: inout [AccountManagerMetadataOperation]) { if let record = record { - let encoder = PostboxEncoder() - encoder.encodeRootObject(record) - withExtendedLifetime(encoder, { - self.valueBox.set(self.table, key: self.key(.currentAuthAccount), value: encoder.readBufferNoCopy()) - }) + let data = try! AdaptedPostboxEncoder().encode(record) + self.valueBox.set(self.table, key: self.key(.currentAuthAccount), value: ReadBuffer(data: data)) } else { self.valueBox.remove(self.table, key: self.key(.currentAuthAccount), secure: false) } diff --git a/submodules/Postbox/Sources/AccountManagerRecordTable.swift b/submodules/Postbox/Sources/AccountManagerRecordTable.swift index c275d523ac..59b79ca8de 100644 --- a/submodules/Postbox/Sources/AccountManagerRecordTable.swift +++ b/submodules/Postbox/Sources/AccountManagerRecordTable.swift @@ -1,10 +1,10 @@ import Foundation -enum AccountManagerRecordOperation { - case set(id: AccountRecordId, record: AccountRecord?) +enum AccountManagerRecordOperation { + case set(id: AccountRecordId, record: AccountRecord?) } -final class AccountManagerRecordTable: Table { +final class AccountManagerRecordTable: Table { static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .int64, compactValuesOnCreation: false) } @@ -15,31 +15,33 @@ final class AccountManagerRecordTable: Table { return result } - func getRecords() -> [AccountRecord] { - var records: [AccountRecord] = [] + func getRecords() -> [AccountRecord] { + var records: [AccountRecord] = [] self.valueBox.scan(self.table, values: { _, value in - let record = AccountRecord(decoder: PostboxDecoder(buffer: value)) - records.append(record) + if let record = try? AdaptedPostboxDecoder().decode(AccountRecord.self, from: value.makeData()) { + records.append(record) + } return true }) return records } - func getRecord(id: AccountRecordId) -> AccountRecord? { + func getRecord(id: AccountRecordId) -> AccountRecord? { if let value = self.valueBox.get(self.table, key: self.key(id)) { - return AccountRecord(decoder: PostboxDecoder(buffer: value)) + if let record = try? AdaptedPostboxDecoder().decode(AccountRecord.self, from: value.makeData()) { + return record + } else { + return nil + } } else { return nil } } - func setRecord(id: AccountRecordId, record: AccountRecord?, operations: inout [AccountManagerRecordOperation]) { + func setRecord(id: AccountRecordId, record: AccountRecord?, operations: inout [AccountManagerRecordOperation]) { if let record = record { - let encoder = PostboxEncoder() - record.encode(encoder) - withExtendedLifetime(encoder, { - self.valueBox.set(self.table, key: self.key(id), value: encoder.readBufferNoCopy()) - }) + let data = try! AdaptedPostboxEncoder().encode(record) + self.valueBox.set(self.table, key: self.key(id), value: ReadBuffer(data: data)) } else { self.valueBox.remove(self.table, key: self.key(id), secure: false) } diff --git a/submodules/Postbox/Sources/AccountRecord.swift b/submodules/Postbox/Sources/AccountRecord.swift index 484c28f8d9..ab8e2d3a29 100644 --- a/submodules/Postbox/Sources/AccountRecord.swift +++ b/submodules/Postbox/Sources/AccountRecord.swift @@ -1,6 +1,6 @@ import Foundation -public protocol AccountRecordAttribute: class, PostboxCoding { +public protocol AccountRecordAttribute: Codable { func isEqual(to: AccountRecordAttribute) -> Bool } @@ -15,10 +15,6 @@ public struct AccountRecordId: Comparable, Hashable, Codable { return self.rawValue } - public var hashValue: Int { - return self.rawValue.hashValue - } - public static func ==(lhs: AccountRecordId, rhs: AccountRecordId) -> Bool { return lhs.rawValue == rhs.rawValue } @@ -34,7 +30,7 @@ public func generateAccountRecordId() -> AccountRecordId { return AccountRecordId(rawValue: id) } -public final class AccountRecord: PostboxCoding, Equatable, Codable { +public final class AccountRecord: Equatable, Codable { enum CodingKeys: String, CodingKey { case id case attributes @@ -42,7 +38,7 @@ public final class AccountRecord: PostboxCoding, Equatable, Codable { } public let id: AccountRecordId - public let attributes: [AccountRecordAttribute] + public let attributes: [Attribute] public let temporarySessionId: Int64? public init(from decoder: Decoder) throws { @@ -53,17 +49,23 @@ public final class AccountRecord: PostboxCoding, Equatable, Codable { self.id = try container.decode(AccountRecordId.self, forKey: .id) } - let attributesData = try container.decode(Array.self, forKey: .attributes) - var attributes: [AccountRecordAttribute] = [] - for data in attributesData { - if let object = PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? AccountRecordAttribute { - attributes.append(object) + if let attributesData = try? container.decode(Array.self, forKey: .attributes) { + var attributes: [Attribute] = [] + for data in attributesData { + if let attribute = try? AdaptedPostboxDecoder().decode(Attribute.self, from: data) { + attributes.append(attribute) + } } + self.attributes = attributes + } else { + let attributes = try container.decode([Attribute].self, forKey: .attributes) + self.attributes = attributes } - self.attributes = attributes - if let temporarySessionIdString = try container.decodeIfPresent(String.self, forKey: .temporarySessionId), let temporarySessionIdValue = Int64(temporarySessionIdString) { + if let temporarySessionIdString = try? container.decodeIfPresent(String.self, forKey: .temporarySessionId), let temporarySessionIdValue = Int64(temporarySessionIdString) { self.temporarySessionId = temporarySessionIdValue + } else if let temporarySessionInt64 = try? container.decodeIfPresent(Int64.self, forKey: .temporarySessionId) { + self.temporarySessionId = temporarySessionInt64 } else { self.temporarySessionId = nil } @@ -72,39 +74,17 @@ public final class AccountRecord: PostboxCoding, Equatable, Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(String("\(self.id.rawValue)"), forKey: .id) - let attributesData: [Data] = self.attributes.map { attribute in - let encoder = PostboxEncoder() - encoder.encodeRootObject(attribute) - return encoder.makeData() - } - try container.encode(attributesData, forKey: .attributes) + try container.encode(self.attributes, forKey: .attributes) let temporarySessionIdString: String? = self.temporarySessionId.flatMap({ "\($0)" }) try container.encodeIfPresent(temporarySessionIdString, forKey: .temporarySessionId) } - public init(id: AccountRecordId, attributes: [AccountRecordAttribute], temporarySessionId: Int64?) { + public init(id: AccountRecordId, attributes: [Attribute], temporarySessionId: Int64?) { self.id = id self.attributes = attributes self.temporarySessionId = temporarySessionId } - public init(decoder: PostboxDecoder) { - self.id = AccountRecordId(rawValue: decoder.decodeInt64ForKey("id", orElse: 0)) - self.attributes = (decoder.decodeObjectArrayForKey("attributes") as [PostboxCoding]).map { $0 as! AccountRecordAttribute } - self.temporarySessionId = decoder.decodeOptionalInt64ForKey("temporarySessionId") - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt64(self.id.int64, forKey: "id") - let attributes: [PostboxCoding] = self.attributes.map { $0 } - encoder.encodeGenericObjectArray(attributes, forKey: "attributes") - if let temporarySessionId = self.temporarySessionId { - encoder.encodeInt64(temporarySessionId, forKey: "temporarySessionId") - } else { - encoder.encodeNil(forKey: "temporarySessionId") - } - } - public static func ==(lhs: AccountRecord, rhs: AccountRecord) -> Bool { if lhs.id != rhs.id { return false diff --git a/submodules/Postbox/Sources/AccountRecordsView.swift b/submodules/Postbox/Sources/AccountRecordsView.swift index 3a86acaf50..b404787231 100644 --- a/submodules/Postbox/Sources/AccountRecordsView.swift +++ b/submodules/Postbox/Sources/AccountRecordsView.swift @@ -1,17 +1,17 @@ import Foundation -final class MutableAccountRecordsView { - fileprivate var records: [AccountRecord] +final class MutableAccountRecordsView { + fileprivate var records: [AccountRecord] fileprivate var currentId: AccountRecordId? - fileprivate var currentAuth: AuthAccountRecord? + fileprivate var currentAuth: AuthAccountRecord? - init(getRecords: () -> [AccountRecord], currentId: AccountRecordId?, currentAuth: AuthAccountRecord?) { + init(getRecords: () -> [AccountRecord], currentId: AccountRecordId?, currentAuth: AuthAccountRecord?) { self.records = getRecords() self.currentId = currentId self.currentAuth = currentAuth } - func replay(operations: [AccountManagerRecordOperation], metadataOperations: [AccountManagerMetadataOperation]) -> Bool { + func replay(operations: [AccountManagerRecordOperation], metadataOperations: [AccountManagerMetadataOperation]) -> Bool { var updated = false for operation in operations { @@ -61,15 +61,15 @@ final class MutableAccountRecordsView { } } -public final class AccountRecordsView { - public let records: [AccountRecord] - public let currentRecord: AccountRecord? - public let currentAuthAccount: AuthAccountRecord? +public final class AccountRecordsView { + public let records: [AccountRecord] + public let currentRecord: AccountRecord? + public let currentAuthAccount: AuthAccountRecord? - init(_ view: MutableAccountRecordsView) { + init(_ view: MutableAccountRecordsView) { self.records = view.records if let currentId = view.currentId { - var currentRecord: AccountRecord? + var currentRecord: AccountRecord? for record in view.records { if record.id == currentId { currentRecord = record diff --git a/submodules/Postbox/Sources/AccountSharedData.swift b/submodules/Postbox/Sources/AccountSharedData.swift index eccca9a837..62676e4ffa 100644 --- a/submodules/Postbox/Sources/AccountSharedData.swift +++ b/submodules/Postbox/Sources/AccountSharedData.swift @@ -1,10 +1,10 @@ import Foundation -final class MutableAccountSharedDataView { +final class MutableAccountSharedDataView { private let keys: Set fileprivate var entries: [ValueBoxKey: PreferencesEntry] = [:] - init(accountManagerImpl: AccountManagerImpl, keys: Set) { + init(accountManagerImpl: AccountManagerImpl, keys: Set) { self.keys = keys for key in keys { if let value = accountManagerImpl.sharedDataTable.get(key: key) { @@ -13,7 +13,7 @@ final class MutableAccountSharedDataView { } } - func replay(accountManagerImpl: AccountManagerImpl, updatedKeys: Set) -> Bool { + func replay(accountManagerImpl: AccountManagerImpl, updatedKeys: Set) -> Bool { var updated = false for key in updatedKeys.intersection(self.keys) { if let value = accountManagerImpl.sharedDataTable.get(key: key) { @@ -27,10 +27,10 @@ final class MutableAccountSharedDataView { } } -public final class AccountSharedDataView { +public final class AccountSharedDataView { public let entries: [ValueBoxKey: PreferencesEntry] - init(_ view: MutableAccountSharedDataView) { + init(_ view: MutableAccountSharedDataView) { self.entries = view.entries } } diff --git a/submodules/Postbox/Sources/Coding.swift b/submodules/Postbox/Sources/Coding.swift index 33edb55996..c7437d6cb9 100644 --- a/submodules/Postbox/Sources/Coding.swift +++ b/submodules/Postbox/Sources/Coding.swift @@ -23,6 +23,12 @@ private let typeStore = { () -> EncodableTypeStore in return _typeStore }() +public func postboxEncodableTypeHash(_ type: Any.Type) -> Int32 { + let string = "\(type)" + let hash = murMurHashString32(string) + return hash +} + public func declareEncodable(_ type: Any.Type, f: @escaping(PostboxDecoder) -> PostboxCoding) { let string = "\(type)" let hash = murMurHashString32(string) @@ -232,6 +238,58 @@ enum ValueType: Int8 { case BytesArray = 13 } +enum ObjectDataValueType { + case Int32 + case Int64 + case Bool + case Double + case String + case Object(hash: Int32) + case Int32Array + case Int64Array + case ObjectArray + case ObjectDictionary + case Bytes + case Nil + case StringArray + case BytesArray +} + +private extension ObjectDataValueType { + init?(_ type: ValueType) { + switch type { + case .Int32: + self = .Int32 + case .Int64: + self = .Int64 + case .Bool: + self = .Bool + case .Double: + self = .Double + case .String: + self = .String + case .Object: + return nil + case .Int32Array: + self = .Int32Array + case .Int64Array: + self = .Int64Array + case .ObjectArray: + self = .ObjectArray + case .ObjectDictionary: + self = .ObjectDictionary + case .Bytes: + self = .Bytes + case .Nil: + self = .Nil + case .StringArray: + self = .StringArray + case .BytesArray: + self = .BytesArray + } + } +} + public final class PostboxEncoder { private let buffer = WriteBuffer() @@ -977,10 +1035,12 @@ public final class PostboxDecoder { } } - func decodeObjectDataForKey(_ key: String) -> (Data, ValueType)? { + func decodeObjectDataForKey(_ key: String) -> (Data, ObjectDataValueType)? { var actualValueType: ValueType = .Nil if PostboxDecoder.positionOnKey(self.buffer.memory, offset: &self.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: nil, actualValueType: &actualValueType, consumeKey: true) { if case .Object = actualValueType { + var hash: Int32 = 0 + memcpy(&hash, self.buffer.memory + self.offset, 4) self.offset += 4 var length: Int32 = 0 @@ -990,14 +1050,14 @@ public final class PostboxDecoder { let innerData = ReadBuffer(memory: self.buffer.memory + self.offset, length: Int(length), freeWhenDone: false).makeData() self.offset += Int(length) - return (innerData, actualValueType) + return (innerData, .Object(hash: hash)) } else { let initialOffset = self.offset PostboxDecoder.skipValue(self.buffer.memory.assumingMemoryBound(to: Int8.self), offset: &self.offset, length: self.buffer.length, valueType: actualValueType) let data = ReadBuffer(memory: UnsafeMutableRawPointer(mutating: self.buffer.memory.advanced(by: initialOffset)), length: self.offset - initialOffset, freeWhenDone: false).makeData() - return (data, actualValueType) + return (data, ObjectDataValueType(actualValueType)!) } } else { return nil diff --git a/submodules/Postbox/Sources/ItemCacheTable.swift b/submodules/Postbox/Sources/ItemCacheTable.swift index 82a782081f..a4607cd63d 100644 --- a/submodules/Postbox/Sources/ItemCacheTable.swift +++ b/submodules/Postbox/Sources/ItemCacheTable.swift @@ -71,6 +71,10 @@ final class ItemCacheTable: Table { self.valueBox.set(self.table, key: self.itemKey(id: id), value: encoder.readBufferNoCopy()) }) } + + func putData(id: ItemCacheEntryId, entry: Data, metaTable: ItemCacheMetaTable) { + self.valueBox.set(self.table, key: self.itemKey(id: id), value: ReadBuffer(data: entry)) + } func retrieve(id: ItemCacheEntryId, metaTable: ItemCacheMetaTable) -> PostboxCoding? { if let value = self.valueBox.get(self.table, key: self.itemKey(id: id)), let entry = PostboxDecoder(buffer: value).decodeRootObject() { @@ -78,6 +82,13 @@ final class ItemCacheTable: Table { } return nil } + + func retrieveData(id: ItemCacheEntryId, metaTable: ItemCacheMetaTable) -> Data? { + if let value = self.valueBox.get(self.table, key: self.itemKey(id: id)) { + return value.makeData() + } + return nil + } func remove(id: ItemCacheEntryId, metaTable: ItemCacheMetaTable) { self.valueBox.remove(self.table, key: self.itemKey(id: id), secure: false) diff --git a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift index 776893cdcd..a7f55d6576 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift @@ -82,16 +82,30 @@ class MessageHistoryThreadsTable: Table { return indices } - func getMessageCountInRange(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { - precondition(lowerBound.id.namespace == namespace) - precondition(upperBound.id.namespace == namespace) - var lowerBoundKey = self.key(threadId: threadId, index: lowerBound) - if lowerBound.timestamp > 1 { - lowerBoundKey = lowerBoundKey.predecessor + func getMessageCountInRange(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex?, upperBound: MessageIndex?) -> Int { + if let lowerBound = lowerBound { + precondition(lowerBound.id.namespace == namespace) } - var upperBoundKey = self.key(threadId: threadId, index: upperBound) - if upperBound.timestamp < Int32.max - 1 { - upperBoundKey = upperBoundKey.successor + if let upperBound = upperBound { + precondition(upperBound.id.namespace == namespace) + } + var lowerBoundKey: ValueBoxKey + if let lowerBound = lowerBound { + lowerBoundKey = self.key(threadId: threadId, index: lowerBound) + if lowerBound.timestamp > 1 { + lowerBoundKey = lowerBoundKey.predecessor + } + } else { + lowerBoundKey = self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace) + } + var upperBoundKey: ValueBoxKey + if let upperBound = upperBound { + upperBoundKey = self.key(threadId: threadId, index: upperBound) + if upperBound.timestamp < Int32.max - 1 { + upperBoundKey = upperBoundKey.successor + } + } else { + upperBoundKey = self.upperBound(threadId: threadId, peerId: peerId, namespace: namespace) } return Int(self.valueBox.count(self.table, start: lowerBoundKey, end: upperBoundKey)) } diff --git a/submodules/Postbox/Sources/NoticeEntryView.swift b/submodules/Postbox/Sources/NoticeEntryView.swift index fcee321560..99c5c84560 100644 --- a/submodules/Postbox/Sources/NoticeEntryView.swift +++ b/submodules/Postbox/Sources/NoticeEntryView.swift @@ -1,15 +1,15 @@ import Foundation -final class MutableNoticeEntryView { +final class MutableNoticeEntryView { private let key: NoticeEntryKey fileprivate var value: NoticeEntry? - init(accountManagerImpl: AccountManagerImpl, key: NoticeEntryKey) { + init(accountManagerImpl: AccountManagerImpl, key: NoticeEntryKey) { self.key = key self.value = accountManagerImpl.noticeTable.get(key: key) } - func replay(accountManagerImpl: AccountManagerImpl, updatedKeys: Set) -> Bool { + func replay(accountManagerImpl: AccountManagerImpl, updatedKeys: Set) -> Bool { if updatedKeys.contains(self.key) { self.value = accountManagerImpl.noticeTable.get(key: self.key) return true @@ -17,15 +17,15 @@ final class MutableNoticeEntryView { return false } - func immutableView() -> NoticeEntryView { + func immutableView() -> NoticeEntryView { return NoticeEntryView(self) } } -public final class NoticeEntryView { +public final class NoticeEntryView { public let value: NoticeEntry? - init(_ view: MutableNoticeEntryView) { + init(_ view: MutableNoticeEntryView) { self.value = view.value } } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index dbd3d51a78..b0222dc12a 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -87,6 +87,22 @@ public final class Transaction { assert(!self.disposed) return self.postbox!.messageHistoryThreadHoleIndexTable.closest(peerId: peerId, threadId: threadId, namespace: namespace, space: .everywhere, range: 1 ... (Int32.max - 1)) } + + public func getThreadMessageCount(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, fromId: Int32?, toIndex: MessageIndex) -> Int? { + assert(!self.disposed) + let fromIndex: MessageIndex? + if let fromId = fromId { + fromIndex = self.postbox!.messageHistoryIndexTable.closestIndex(id: MessageId(peerId: peerId, namespace: namespace, id: fromId)) + } else { + fromIndex = nil + } + + if let fromIndex = fromIndex { + return self.postbox!.messageHistoryThreadsTable.getMessageCountInRange(threadId: threadId, peerId: peerId, namespace: namespace, lowerBound: fromIndex, upperBound: toIndex) + } else { + return nil + } + } public func doesChatListGroupContainHoles(groupId: PeerGroupId) -> Bool { assert(!self.disposed) @@ -696,6 +712,11 @@ public final class Transaction { assert(!self.disposed) self.postbox?.putItemCacheEntry(id: id, entry: entry, collectionSpec: collectionSpec) } + + public func putItemCacheEntryData(id: ItemCacheEntryId, entry: Data, collectionSpec: ItemCacheCollectionSpec) { + assert(!self.disposed) + self.postbox?.putItemCacheEntryData(id: id, entry: entry, collectionSpec: collectionSpec) + } public func removeItemCacheEntry(id: ItemCacheEntryId) { assert(!self.disposed) @@ -706,6 +727,11 @@ public final class Transaction { assert(!self.disposed) return self.postbox?.retrieveItemCacheEntry(id: id) } + + public func retrieveItemCacheEntryData(id: ItemCacheEntryId) -> Data? { + assert(!self.disposed) + return self.postbox?.retrieveItemCacheEntryData(id: id) + } public func clearItemCacheCollection(collectionId: ItemCacheCollectionId) { assert(!self.disposed) @@ -2316,10 +2342,19 @@ public final class Postbox { self.itemCacheTable.put(id: id, entry: entry, metaTable: self.itemCacheMetaTable) self.currentUpdatedCacheEntryKeys.insert(id) } + + fileprivate func putItemCacheEntryData(id: ItemCacheEntryId, entry: Data, collectionSpec: ItemCacheCollectionSpec) { + self.itemCacheTable.putData(id: id, entry: entry, metaTable: self.itemCacheMetaTable) + self.currentUpdatedCacheEntryKeys.insert(id) + } func retrieveItemCacheEntry(id: ItemCacheEntryId) -> PostboxCoding? { return self.itemCacheTable.retrieve(id: id, metaTable: self.itemCacheMetaTable) } + + func retrieveItemCacheEntryData(id: ItemCacheEntryId) -> Data? { + return self.itemCacheTable.retrieveData(id: id, metaTable: self.itemCacheMetaTable) + } func clearItemCacheCollection(collectionId: ItemCacheCollectionId) { return self.itemCacheTable.removeAll(collectionId: collectionId) diff --git a/submodules/Postbox/Sources/Utils/Decoder/AdaptedPostboxDecoder.swift b/submodules/Postbox/Sources/Utils/Decoder/AdaptedPostboxDecoder.swift index 6b83b7b0d5..23e6b1475d 100644 --- a/submodules/Postbox/Sources/Utils/Decoder/AdaptedPostboxDecoder.swift +++ b/submodules/Postbox/Sources/Utils/Decoder/AdaptedPostboxDecoder.swift @@ -10,6 +10,16 @@ final public class AdaptedPostboxDecoder { case dataArray } + public final class RawObjectData: Codable { + public let data: Data + public let typeHash: Int32 + + public init(data: Data, typeHash: Int32) { + self.data = data + self.typeHash = typeHash + } + } + public init() { } @@ -24,7 +34,7 @@ final public class AdaptedPostboxDecoder { } extension AdaptedPostboxDecoder.ContentType { - init?(valueType: ValueType) { + init?(valueType: ObjectDataValueType) { switch valueType { case .Int32: return nil diff --git a/submodules/Postbox/Sources/Utils/Decoder/AdaptedPostboxKeyedDecodingContainer.swift b/submodules/Postbox/Sources/Utils/Decoder/AdaptedPostboxKeyedDecodingContainer.swift index 22e5e62c32..63a79a17c7 100644 --- a/submodules/Postbox/Sources/Utils/Decoder/AdaptedPostboxKeyedDecodingContainer.swift +++ b/submodules/Postbox/Sources/Utils/Decoder/AdaptedPostboxKeyedDecodingContainer.swift @@ -35,6 +35,14 @@ extension _AdaptedPostboxDecoder.KeyedContainer: KeyedDecodingContainerProtocol func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { if let (data, valueType) = self.decoder.decodeObjectDataForKey(key.stringValue) { + if type == AdaptedPostboxDecoder.RawObjectData.self { + if case let .Object(typeHash) = valueType { + return AdaptedPostboxDecoder.RawObjectData(data: data, typeHash: typeHash) as! T + } else { + decodingErrorBreakpoint() + throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: self.codingPath + [key], debugDescription: "")) + } + } if let mappedType = AdaptedPostboxDecoder.ContentType(valueType: valueType) { return try AdaptedPostboxDecoder().decode(T.self, from: data, contentType: mappedType) } else { diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index 6f569eac70..d8856e042d 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -160,6 +160,12 @@ public func combineLatest( }, initialValues: [:], queue: queue) } +public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal, _ s13: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13) + }, initialValues: [:], queue: queue) +} + public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { return single([T](), E.self) diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift index 014cdfdbad..9d46e3f156 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift @@ -313,7 +313,7 @@ public func proxySettingsController(context: AccountContext, mode: ProxySettings return proxySettingsController(accountManager: context.sharedContext.accountManager, context: context, postbox: context.account.postbox, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData) } -public func proxySettingsController(accountManager: AccountManager, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal) -> ViewController { +public func proxySettingsController(accountManager: AccountManager, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal) -> ViewController { var pushControllerImpl: ((ViewController) -> Void)? var dismissImpl: (() -> Void)? let stateValue = Atomic(value: ProxySettingsControllerState()) diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerActionSheetController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerActionSheetController.swift index 1b56234871..f4c1aee48b 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerActionSheetController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerActionSheetController.swift @@ -28,7 +28,7 @@ public final class ProxyServerActionSheetController: ActionSheetController { self.init(presentationData: presentationData, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, server: server, updatedPresentationData: context.sharedContext.presentationData) } - public init(presentationData: PresentationData, accountManager: AccountManager, postbox: Postbox, network: Network, server: ProxyServerSettings, updatedPresentationData: Signal?) { + public init(presentationData: PresentationData, accountManager: AccountManager, postbox: Postbox, network: Network, server: ProxyServerSettings, updatedPresentationData: Signal?) { let sheetTheme = ActionSheetControllerTheme(presentationData: presentationData) super.init(theme: sheetTheme) @@ -262,7 +262,7 @@ private final class ProxyServerInfoItemNode: ActionSheetItemNode { } private final class ProxyServerActionItem: ActionSheetItem { - private let accountManager: AccountManager + private let accountManager: AccountManager private let postbox: Postbox private let network: Network private let presentationData: PresentationData @@ -270,7 +270,7 @@ private final class ProxyServerActionItem: ActionSheetItem { private let dismiss: (Bool) -> Void private let present: (ViewController, Any?) -> Void - init(accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.accountManager = accountManager self.postbox = postbox self.network = network @@ -289,7 +289,7 @@ private final class ProxyServerActionItem: ActionSheetItem { } private final class ProxyServerActionItemNode: ActionSheetItemNode { - private let accountManager: AccountManager + private let accountManager: AccountManager private let postbox: Postbox private let network: Network private let presentationData: PresentationData @@ -305,7 +305,7 @@ private final class ProxyServerActionItemNode: ActionSheetItemNode { private let disposable = MetaDisposable() private var revertSettings: ProxySettings? - init(accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, theme: ActionSheetControllerTheme, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, theme: ActionSheetControllerTheme, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.accountManager = accountManager self.postbox = postbox self.network = network diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift index 5ebc2a616f..82d43c92ef 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift @@ -269,7 +269,7 @@ public func proxyServerSettingsController(context: AccountContext, currentSettin return proxyServerSettingsController(context: context, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, currentSettings: currentSettings) } -func proxyServerSettingsController(context: AccountContext? = nil, presentationData: PresentationData, updatedPresentationData: Signal, accountManager: AccountManager, postbox: Postbox, network: Network, currentSettings: ProxyServerSettings?) -> ViewController { +func proxyServerSettingsController(context: AccountContext? = nil, presentationData: PresentationData, updatedPresentationData: Signal, accountManager: AccountManager, postbox: Postbox, network: Network, currentSettings: ProxyServerSettings?) -> ViewController { var currentMode: ProxyServerSettingsControllerMode = .socks5 var currentUsername: String? var currentPassword: String? diff --git a/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift b/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift index bb6825a4f6..71019a4cc9 100644 --- a/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift +++ b/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift @@ -130,7 +130,7 @@ public class LocalizationListController: ViewController { } } - self.controllerNode.listNode.didEndScrolling = { [weak self] in + self.controllerNode.listNode.didEndScrolling = { [weak self] _ in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { let _ = fixNavigationSearchableListNodeScrolling(strongSelf.controllerNode.listNode, searchNode: searchContentNode) } diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift index d4c623d5b2..9b00044f63 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift @@ -142,7 +142,7 @@ public class NotificationExceptionsController: ViewController { } } - self.controllerNode.listNode.didEndScrolling = { [weak self] in + self.controllerNode.listNode.didEndScrolling = { [weak self] _ in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { let _ = fixNavigationSearchableListNodeScrolling(strongSelf.controllerNode.listNode, searchNode: searchContentNode) } 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/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index 56734e03a1..e9413858f4 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -10,6 +10,7 @@ import TelegramUIPreferences import ItemListUI import PresentationDataUtils import AccountContext +import WallpaperBackgroundNode class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem { let context: AccountContext @@ -75,7 +76,7 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem { } class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { - private let backgroundNode: ASImageNode + private var backgroundNode: WallpaperBackgroundNode? private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let maskNode: ASImageNode @@ -91,12 +92,6 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { private var item: ForwardPrivacyChatPreviewItem? init() { - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.contentMode = .scaleAspectFill - self.topStripeNode = ASDisplayNode() self.topStripeNode.isLayerBacked = true @@ -130,21 +125,17 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { } func asyncLayout() -> (_ item: ForwardPrivacyChatPreviewItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { - let currentItem = self.item - let currentNode = self.messageNode let makeTextLayout = TextNode.asyncLayout(self.measureTextNode) + + var currentBackgroundNode = self.backgroundNode return { item, params, neighbors in - var updatedBackgroundImage: UIImage? - var backgroundImageContentMode = UIView.ContentMode.scaleAspectFill - if currentItem?.wallpaper != item.wallpaper { - updatedBackgroundImage = chatControllerBackgroundImage(theme: item.theme, wallpaper: item.wallpaper, mediaBox: item.context.sharedContext.accountManager.mediaBox, knockoutMode: item.context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) - - if case .gradient = item.wallpaper { - backgroundImageContentMode = .scaleToFill - } + if currentBackgroundNode == nil { + currentBackgroundNode = WallpaperBackgroundNode(context: item.context) } + currentBackgroundNode?.update(wallpaper: item.wallpaper) + currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners) let insets: UIEdgeInsets let separatorHeight = UIScreenPixel @@ -158,7 +149,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName, psaType: nil, flags: []) - let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: nil) + let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode) var node: ListViewItemNode? if let current = currentNode { @@ -227,17 +218,15 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { node.updateFrame(CGRect(origin: CGPoint(x: 0.0, y: topOffset), size: node.frame.size), within: layout.contentSize) topOffset += node.frame.size.height } - - if let updatedBackgroundImage = updatedBackgroundImage { - strongSelf.backgroundNode.image = updatedBackgroundImage - strongSelf.backgroundNode.contentMode = backgroundImageContentMode - } + strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor - - if strongSelf.backgroundNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + + if let currentBackgroundNode = currentBackgroundNode, strongSelf.backgroundNode !== currentBackgroundNode { + strongSelf.backgroundNode = currentBackgroundNode + strongSelf.insertSubnode(currentBackgroundNode, at: 0) } + if strongSelf.topStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) } @@ -273,7 +262,14 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) - strongSelf.backgroundNode.frame = backgroundFrame.insetBy(dx: 0.0, dy: -100.0) + + if let backgroundNode = strongSelf.backgroundNode { + backgroundNode.frame = backgroundFrame.insetBy(dx: 0.0, dy: -100.0) + backgroundNode.update(wallpaper: item.wallpaper) + backgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners) + backgroundNode.updateLayout(size: backgroundNode.bounds.size, transition: .immediate) + } + strongSelf.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) 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/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index 7d1c27992f..b176103e2f 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -323,6 +323,8 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll var generalThemeReference: PresentationThemeReference? if case let .edit(cloudTheme) = mode { generalThemeReference = PresentationThemeReference.cloud(cloudTheme).generalThemeReference + } else if case let .create(existingTheme, _) = mode, existingTheme == nil { + generalThemeReference = PresentationThemeReference.builtin(presentationData.theme.referenceTheme) } let arguments = EditThemeControllerArguments(context: context, updateState: { f in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift index 10bc360375..62f2e4888b 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift @@ -14,12 +14,8 @@ import MediaResources private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e] extension TelegramThemeSettings { - convenience init(baseTheme: TelegramBaseTheme, accentColor: UIColor, messageColors: (top: UIColor, bottom: UIColor?)?, wallpaper: TelegramWallpaper?) { - var messageColorsValues: (UInt32, UInt32)? - if let colors = messageColors { - messageColorsValues = (colors.0.argb, colors.1?.argb ?? colors.0.argb) - } - self.init(baseTheme: baseTheme, accentColor: accentColor.argb, messageColors: messageColorsValues, wallpaper: wallpaper) + convenience init(baseTheme: TelegramBaseTheme, accentColor: UIColor, messageColors: [UInt32], animateMessageColors: Bool, wallpaper: TelegramWallpaper?) { + self.init(baseTheme: baseTheme, accentColor: accentColor.argb, messageColors: messageColors, animateMessageColors: animateMessageColors, wallpaper: wallpaper) } } @@ -159,11 +155,11 @@ final class ThemeAccentColorController: ViewController { var coloredWallpaper: TelegramWallpaper? if !state.backgroundColors.isEmpty { if let patternWallpaper = state.patternWallpaper { - coloredWallpaper = patternWallpaper.withUpdatedSettings(WallpaperSettings(colors: state.backgroundColors, intensity: state.patternIntensity, rotation: state.rotation)) + coloredWallpaper = patternWallpaper.withUpdatedSettings(WallpaperSettings(colors: state.backgroundColors.map { $0.rgb }, intensity: state.patternIntensity, rotation: state.rotation)) } else if state.backgroundColors.count >= 2 { - coloredWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: state.backgroundColors, settings: WallpaperSettings(rotation: state.rotation))) + coloredWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: state.backgroundColors.map { $0.rgb }, settings: WallpaperSettings(rotation: state.rotation))) } else { - coloredWallpaper = .color(state.backgroundColors[0]) + coloredWallpaper = .color(state.backgroundColors[0].rgb) } } @@ -190,8 +186,8 @@ final class ThemeAccentColorController: ViewController { if case let .edit(theme, _, generalThemeReference, _, _, completion) = strongSelf.mode { let _ = (prepareWallpaper - |> deliverOnMainQueue).start(completed: { let updatedTheme: PresentationTheme - + |> deliverOnMainQueue).start(completed: { + let updatedTheme: PresentationTheme var settings: TelegramThemeSettings? var hasSettings = false var baseTheme: TelegramBaseTheme? @@ -205,13 +201,13 @@ final class ThemeAccentColorController: ViewController { } if let themeReference = generalThemeReference { - updatedTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: state.accentColor, backgroundColors: state.backgroundColors, bubbleColors: state.messagesColors, wallpaper: coloredWallpaper ?? state.initialWallpaper, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme + updatedTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: state.accentColor.color, backgroundColors: state.backgroundColors.map { $0.rgb }, bubbleColors: state.messagesColors.map { $0.rgb }, animateBubbleColors: state.animateMessageColors, wallpaper: coloredWallpaper ?? state.initialWallpaper, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme } else { - updatedTheme = customizePresentationTheme(theme, editing: false, accentColor: state.accentColor, backgroundColors: state.backgroundColors, bubbleColors: state.messagesColors, wallpaper: state.initialWallpaper ?? coloredWallpaper) + updatedTheme = customizePresentationTheme(theme, editing: false, accentColor: state.accentColor.color, backgroundColors: state.backgroundColors.map { $0.rgb }, bubbleColors: state.messagesColors.map { $0.rgb }, animateBubbleColors: state.animateMessageColors, wallpaper: state.initialWallpaper ?? coloredWallpaper) } if hasSettings, let baseTheme = baseTheme { - settings = TelegramThemeSettings(baseTheme: baseTheme, accentColor: state.accentColor, messageColors: state.messagesColors, wallpaper: coloredWallpaper) + settings = TelegramThemeSettings(baseTheme: baseTheme, accentColor: state.accentColor.color, messageColors: state.messagesColors.map { $0.rgb }, animateMessageColors: state.animateMessageColors, wallpaper: coloredWallpaper) } completion(updatedTheme, settings) @@ -230,12 +226,12 @@ final class ThemeAccentColorController: ViewController { let wallpaper = coloredWallpaper ?? state.initialWallpaper - let settings = TelegramThemeSettings(baseTheme: baseTheme, accentColor: state.accentColor, messageColors: state.messagesColors, wallpaper: wallpaper) + let settings = TelegramThemeSettings(baseTheme: baseTheme, accentColor: state.accentColor.rgb, messageColors: state.messagesColors.map { $0.rgb }, animateMessageColors: state.animateMessageColors, wallpaper: wallpaper) let baseThemeReference = PresentationThemeReference.builtin(PresentationBuiltinThemeReference(baseTheme: baseTheme)) let apply: Signal if create { - apply = (prepareWallpaper |> then(createTheme(account: context.account, title: generateThemeName(accentColor: state.accentColor), resource: nil, thumbnailData: nil, settings: settings))) + apply = (prepareWallpaper |> then(createTheme(account: context.account, title: generateThemeName(accentColor: state.accentColor.color), resource: nil, thumbnailData: nil, settings: settings))) |> mapToSignal { next -> Signal in if case let .result(resultTheme) = next { let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() @@ -379,9 +375,10 @@ final class ThemeAccentColorController: ViewController { var backgroundColors: [UInt32] = [] var patternWallpaper: TelegramWallpaper? var patternIntensity: Int32 = 50 - let messageColors: (UIColor, UIColor?)? + let messageColors: [UInt32] let defaultMessagesColor: UIColor? = nil var rotation: Int32 = 0 + let animateMessageColors: Bool func extractWallpaperParameters(_ wallpaper: TelegramWallpaper?) { guard let wallpaper = wallpaper else { @@ -432,7 +429,7 @@ final class ThemeAccentColorController: ViewController { } } - if case .colors(_, true) = strongSelf.mode { + if case let .colors(initialThemeReference, true) = strongSelf.mode { let themeSpecificAccentColor = settings.themeSpecificAccentColors[themeReference.index] accentColor = themeSpecificAccentColor?.color ?? defaultDayAccentColor @@ -447,6 +444,14 @@ final class ThemeAccentColorController: ViewController { wallpaper = theme.chat.defaultWallpaper } + if case let .cloud(cloudTheme) = initialThemeReference, let settings = cloudTheme.theme.settings { + animateMessageColors = settings.animateMessageColors + } else if let referenceTheme = referenceTheme { + animateMessageColors = referenceTheme.chat.animateMessageColors + } else { + animateMessageColors = false + } + extractBuiltinWallpaper(wallpaper) if !wallpaper.isColorOrGradient { @@ -459,19 +464,15 @@ final class ThemeAccentColorController: ViewController { extractWallpaperParameters(wallpaper) } - if let bubbleColors = settings.themeSpecificAccentColors[themeReference.index]?.customBubbleColors { - if let bottomColor = bubbleColors.1 { - messageColors = (bubbleColors.0, bottomColor) - } else { - messageColors = (bubbleColors.0, nil) - } + if let bubbleColors = settings.themeSpecificAccentColors[themeReference.index]?.customBubbleColors, !bubbleColors.isEmpty { + messageColors = bubbleColors } else { if let themeReference = strongSelf.mode.themeReference, themeReference == .builtin(.dayClassic), settings.themeSpecificAccentColors[themeReference.index] == nil { - messageColors = (UIColor(rgb: 0xe1ffc7), nil) + messageColors = [UIColor(rgb: 0xe1ffc7).rgb] } else if let referenceTheme = referenceTheme { - messageColors = (referenceTheme.chat.message.outgoing.bubble.withoutWallpaper.fill, referenceTheme.chat.message.outgoing.bubble.withoutWallpaper.gradientFill) + messageColors = referenceTheme.chat.message.outgoing.bubble.withoutWallpaper.fill.map(\.rgb) } else { - messageColors = nil + messageColors = [] } } } else { @@ -489,17 +490,13 @@ final class ThemeAccentColorController: ViewController { initialWallpaper = wallpaper } - if let colors = themeSettings.messageColors { - let topMessageColor = UIColor(argb: colors.top) - let bottomMessageColor = UIColor(argb: colors.bottom) - if topMessageColor.argb == bottomMessageColor.argb { - messageColors = (topMessageColor, nil) - } else { - messageColors = (topMessageColor, bottomMessageColor) - } + if !themeSettings.messageColors.isEmpty { + messageColors = themeSettings.messageColors } else { - messageColors = nil + messageColors = [] } + + animateMessageColors = themeSettings.animateMessageColors } else if case .builtin = themeReference { let themeSpecificAccentColor = settings.themeSpecificAccentColors[themeReference.index] accentColor = themeSpecificAccentColor?.color ?? defaultDayAccentColor @@ -525,19 +522,17 @@ final class ThemeAccentColorController: ViewController { extractWallpaperParameters(wallpaper) } - if let bubbleColors = settings.themeSpecificAccentColors[themeReference.index]?.customBubbleColors { - if let bottomColor = bubbleColors.1 { - messageColors = (bubbleColors.0, bottomColor) - } else { - messageColors = (bubbleColors.0, nil) - } + if let bubbleColors = settings.themeSpecificAccentColors[themeReference.index]?.customBubbleColors, !bubbleColors.isEmpty { + messageColors = bubbleColors } else { if let themeReference = strongSelf.mode.themeReference, themeReference == .builtin(.dayClassic), settings.themeSpecificAccentColors[themeReference.index] == nil { - messageColors = (UIColor(rgb: 0xe1ffc7), nil) + messageColors = [UIColor(rgb: 0xe1ffc7).rgb] } else { - messageColors = nil + messageColors = [] } } + + animateMessageColors = false } else { let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeReference)! @@ -550,14 +545,9 @@ final class ThemeAccentColorController: ViewController { initialWallpaper = wallpaper } - let topMessageColor = theme.chat.message.outgoing.bubble.withWallpaper.fill - let bottomMessageColor = theme.chat.message.outgoing.bubble.withWallpaper.gradientFill + messageColors = theme.chat.message.outgoing.bubble.withWallpaper.fill.map(\.rgb) - if topMessageColor.argb == bottomMessageColor.argb { - messageColors = (topMessageColor, nil) - } else { - messageColors = (topMessageColor, bottomMessageColor) - } + animateMessageColors = theme.chat.animateMessageColors } } } else if case let .edit(theme, wallpaper, _, _, _, _) = strongSelf.mode { @@ -570,21 +560,18 @@ final class ThemeAccentColorController: ViewController { initialWallpaper = wallpaper } - let topMessageColor = theme.chat.message.outgoing.bubble.withWallpaper.fill - let bottomMessageColor = theme.chat.message.outgoing.bubble.withWallpaper.gradientFill + messageColors = theme.chat.message.outgoing.bubble.withWallpaper.fill.map(\.rgb) - if topMessageColor.argb == bottomMessageColor.argb { - messageColors = (topMessageColor, nil) - } else { - messageColors = (topMessageColor, bottomMessageColor) - } + animateMessageColors = theme.chat.animateMessageColors } else { accentColor = defaultDayAccentColor backgroundColors = [] - messageColors = nil + messageColors = [] + + animateMessageColors = false } - let initialState = ThemeColorState(section: strongSelf.section, accentColor: accentColor, initialWallpaper: initialWallpaper, backgroundColors: backgroundColors, patternWallpaper: patternWallpaper, patternIntensity: patternIntensity, defaultMessagesColor: defaultMessagesColor, messagesColors: messageColors, rotation: rotation) + let initialState = ThemeColorState(section: strongSelf.section, accentColor: HSBColor(color: accentColor), initialWallpaper: initialWallpaper, backgroundColors: backgroundColors.map { HSBColor(rgb: $0) }, patternWallpaper: patternWallpaper, patternIntensity: patternIntensity, animateMessageColors: animateMessageColors, defaultMessagesColor: defaultMessagesColor.flatMap { HSBColor(color: $0) }, messagesColors: messageColors.map { HSBColor(rgb: $0) }, selectedColor: 0, rotation: rotation) strongSelf.controllerNode.updateState({ _ in return initialState diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index e4573af7ce..0c4c034718 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -39,17 +39,20 @@ struct ThemeColorState { fileprivate var colorPanelCollapsed: Bool fileprivate var displayPatternPanel: Bool - var accentColor: UIColor + var accentColor: HSBColor var initialWallpaper: TelegramWallpaper? - var backgroundColors: [UInt32] + var backgroundColors: [HSBColor] fileprivate var preview: Bool fileprivate var previousPatternWallpaper: TelegramWallpaper? var patternWallpaper: TelegramWallpaper? var patternIntensity: Int32 - var defaultMessagesColor: UIColor? - var messagesColors: (UIColor, UIColor?)? + var animateMessageColors: Bool + var defaultMessagesColor: HSBColor? + var messagesColors: [HSBColor] + + var selectedColor: Int var rotation: Int32 @@ -57,19 +60,21 @@ struct ThemeColorState { self.section = nil self.colorPanelCollapsed = false self.displayPatternPanel = false - self.accentColor = .clear + self.accentColor = HSBColor(hue: 0.0, saturation: 0.0, brightness: 1.0) self.initialWallpaper = nil self.backgroundColors = [] self.preview = false self.previousPatternWallpaper = nil self.patternWallpaper = nil self.patternIntensity = 50 + self.animateMessageColors = false self.defaultMessagesColor = nil - self.messagesColors = nil + self.messagesColors = [] + self.selectedColor = 0 self.rotation = 0 } - init(section: ThemeColorSection, accentColor: UIColor, initialWallpaper: TelegramWallpaper?, backgroundColors: [UInt32], patternWallpaper: TelegramWallpaper?, patternIntensity: Int32, defaultMessagesColor: UIColor?, messagesColors: (UIColor, UIColor?)?, rotation: Int32 = 0) { + init(section: ThemeColorSection, accentColor: HSBColor, initialWallpaper: TelegramWallpaper?, backgroundColors: [HSBColor], patternWallpaper: TelegramWallpaper?, patternIntensity: Int32, animateMessageColors: Bool, defaultMessagesColor: HSBColor?, messagesColors: [HSBColor], selectedColor: Int = 0, rotation: Int32 = 0) { self.section = section self.colorPanelCollapsed = false self.displayPatternPanel = false @@ -80,8 +85,10 @@ struct ThemeColorState { self.previousPatternWallpaper = nil self.patternWallpaper = patternWallpaper self.patternIntensity = patternIntensity + self.animateMessageColors = animateMessageColors self.defaultMessagesColor = defaultMessagesColor self.messagesColors = messagesColors + self.selectedColor = selectedColor self.rotation = rotation } @@ -98,25 +105,19 @@ struct ThemeColorState { if self.patternIntensity != otherState.patternIntensity { return false } - if self.rotation != otherState.rotation { + if self.animateMessageColors != otherState.animateMessageColors { return false } if self.backgroundColors != otherState.backgroundColors { return false } - - if let lhsMessagesColors = self.messagesColors, let rhsMessagesColors = otherState.messagesColors { - if lhsMessagesColors.0 != rhsMessagesColors.0 { - return false - } - if let lhsSecondColor = lhsMessagesColors.1, let rhsSecondColor = rhsMessagesColors.1 { - if lhsSecondColor != rhsSecondColor { - return false - } - } else if (lhsMessagesColors.1 == nil) != (rhsMessagesColors.1 == nil) { - return false - } - } else if (self.messagesColors == nil) != (otherState.messagesColors == nil) { + if self.messagesColors != otherState.messagesColors { + return false + } + if self.selectedColor != otherState.selectedColor { + return false + } + if self.rotation != otherState.rotation { return false } return true @@ -126,9 +127,9 @@ struct ThemeColorState { private func calcPatternColors(for state: ThemeColorState) -> [UIColor] { if state.backgroundColors.count >= 1 { let patternIntensity = CGFloat(state.patternIntensity) / 100.0 - let topPatternColor = UIColor(rgb: state.backgroundColors[0]).withAlphaComponent(patternIntensity) + let topPatternColor = state.backgroundColors[0].color.withAlphaComponent(patternIntensity) if state.backgroundColors.count >= 2 { - let bottomPatternColor = UIColor(rgb: state.backgroundColors[1]).withAlphaComponent(patternIntensity) + let bottomPatternColor = state.backgroundColors[1].color.withAlphaComponent(patternIntensity) return [topPatternColor, bottomPatternColor] } else { return [topPatternColor, topPatternColor] @@ -186,7 +187,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate private let serviceBackgroundColorPromise = Promise() private var wallpaperDisposable = MetaDisposable() - private var currentBackgroundColors: ([UInt32], Int32?, Int32?)? + private var currentBackgroundColors: ([HSBColor], Int32?, Int32?)? private var currentBackgroundPromise = Promise<(UIColor, UIColor?)?>() private var patternWallpaper: TelegramWallpaper? @@ -334,7 +335,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate self.colorsButtonNode.addTarget(self, action: #selector(self.toggleColors), forControlEvents: .touchUpInside) self.playButtonNode.addTarget(self, action: #selector(self.playPressed), forControlEvents: .touchUpInside) - self.colorPanelNode.colorsChanged = { [weak self] colors, ended in + self.colorPanelNode.colorsChanged = { [weak self] colors, selectedColor, ended in if let strongSelf = self, let section = strongSelf.state.section { strongSelf.updateState({ current in var updated = current @@ -342,19 +343,16 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate switch section { case .accent: if let firstColor = colors.first { - updated.accentColor = UIColor(rgb: firstColor) + updated.accentColor = firstColor } case .background: updated.backgroundColors = colors case .messages: - if colors.count >= 1 { - updated.messagesColors = (UIColor(rgb: colors[0]), colors.count >= 2 ? UIColor(rgb: colors[1]) : nil) - } else { - updated.messagesColors = nil - } + updated.messagesColors = colors } + updated.selectedColor = selectedColor return updated - }) + }, animated: true) } } @@ -434,7 +432,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate |> mapToThrottled { next -> Signal in return .single(next) |> then(.complete() |> delay(0.0166667, queue: self.queue)) } - |> map { state -> (PresentationTheme?, TelegramWallpaper, UIColor, [UInt32], Int32, PatternWallpaperArguments, Bool) in + |> map { state -> (PresentationTheme?, TelegramWallpaper, UIColor, [HSBColor], Int32, PatternWallpaperArguments, Bool) in let accentColor = state.accentColor var backgroundColors = state.backgroundColors let messagesColors = state.messagesColors @@ -448,7 +446,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate if !backgroundColors.isEmpty { if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper { - wallpaper = patternWallpaper.withUpdatedSettings(WallpaperSettings(colors: backgroundColors, intensity: state.patternIntensity, rotation: state.rotation)) + wallpaper = patternWallpaper.withUpdatedSettings(WallpaperSettings(colors: backgroundColors.map { $0.rgb }, intensity: state.patternIntensity, rotation: state.rotation)) let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100) var convertedRepresentations: [ImageRepresentationWithReference] = [] @@ -457,22 +455,22 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: nil), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource))) } else if backgroundColors.count >= 2 { - wallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: backgroundColors, settings: WallpaperSettings(rotation: state.rotation))) + wallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: backgroundColors.map { $0.rgb }, settings: WallpaperSettings(rotation: state.rotation))) } else { - wallpaper = .color(backgroundColors.first ?? 0xffffff) + wallpaper = .color(backgroundColors.first?.rgb ?? 0xffffff) } } else if let themeReference = mode.themeReference, case let .builtin(theme) = themeReference, state.initialWallpaper == nil { var suggestedWallpaper: TelegramWallpaper switch theme { case .dayClassic: - let topColor = accentColor.withMultiplied(hue: 1.010, saturation: 0.414, brightness: 0.957) - let bottomColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.867, brightness: 0.965) + let topColor = HSBColor(color: accentColor.color.withMultiplied(hue: 1.010, saturation: 0.414, brightness: 0.957)) + let bottomColor = HSBColor(color: accentColor.color.withMultiplied(hue: 1.019, saturation: 0.867, brightness: 0.965)) suggestedWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: [topColor.rgb, bottomColor.rgb], settings: WallpaperSettings())) - backgroundColors = [topColor.rgb, bottomColor.rgb] + backgroundColors = [topColor, bottomColor] case .nightAccent: - let color = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18) + let color = HSBColor(color: accentColor.color.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18)) suggestedWallpaper = .color(color.rgb) - backgroundColors = [color.rgb] + backgroundColors = [color] default: suggestedWallpaper = .builtin(WallpaperSettings()) } @@ -486,9 +484,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate if !updateOnlyWallpaper { if let themeReference = mode.themeReference { - updatedTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: messagesColors, serviceBackgroundColor: serviceBackgroundColor, preview: true) ?? defaultPresentationTheme + updatedTheme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, accentColor: accentColor.color, backgroundColors: backgroundColors.map { $0.rgb }, bubbleColors: messagesColors.map { $0.rgb }, animateBubbleColors: state.animateMessageColors, serviceBackgroundColor: serviceBackgroundColor, preview: true) ?? defaultPresentationTheme } else if case let .edit(theme, _, _, _, _, _) = mode { - updatedTheme = customizePresentationTheme(theme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: messagesColors) + updatedTheme = customizePresentationTheme(theme, editing: false, accentColor: accentColor.color, backgroundColors: backgroundColors.map { $0.rgb }, bubbleColors: messagesColors.map { $0.rgb }, animateBubbleColors: state.animateMessageColors) } else { updatedTheme = theme } @@ -530,8 +528,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate strongSelf.wallpaper = wallpaper strongSelf.patternArguments = patternArguments - strongSelf.colorsButtonNode.colors = backgroundColors.map(UIColor.init(rgb:)) - if !preview { if !backgroundColors.isEmpty { strongSelf.currentBackgroundColors = (backgroundColors, strongSelf.state.rotation, strongSelf.state.patternIntensity) @@ -587,6 +583,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate override func didLoad() { super.didLoad() + self.view.disablesInteractiveModalDismiss = true + self.scrollNode.view.bounces = false self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true self.scrollNode.view.showsHorizontalScrollIndicator = false @@ -604,6 +602,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } } + private var crossfadeBubbles = false func updateState(_ f: (ThemeColorState) -> ThemeColorState, animated: Bool = false) { let previousState = self.state self.state = f(self.state) @@ -625,26 +624,36 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } } - if (previousState.patternWallpaper != nil) != (self.state.patternWallpaper != nil) { - self.patternButtonNode.setSelected(self.state.patternWallpaper != nil, animated: animated) + let sectionChanged = previousState.section != self.state.section + + if self.state.section == .background { + if (previousState.patternWallpaper != nil) != (self.state.patternWallpaper != nil) || sectionChanged { + self.patternButtonNode.setSelected(self.state.patternWallpaper != nil, animated: animated) + } + } else if self.state.section == .messages { + if previousState.animateMessageColors != self.state.animateMessageColors || sectionChanged { + self.crossfadeBubbles = true + self.patternButtonNode.setSelected(self.state.animateMessageColors, animated: animated) + } } - let sectionChanged = previousState.section != self.state.section if sectionChanged, let section = self.state.section { self.view.endEditing(true) - var colors: [UInt32] - var defaultColor: UIColor? + self.patternButtonNode.title = section == .messages ? self.presentationData.strings.WallpaperPreview_Animate : self.presentationData.strings.WallpaperPreview_Pattern + + var colors: [HSBColor] + var defaultColor: HSBColor? switch section { case .accent: - colors = [self.state.accentColor.rgb] + colors = [self.state.accentColor] case .background: if let themeReference = self.mode.themeReference, case let .builtin(theme) = themeReference { switch theme { case .dayClassic: - defaultColor = self.state.accentColor.withMultiplied(hue: 1.019, saturation: 0.867, brightness: 0.965) + defaultColor = HSBColor(color: self.state.accentColor.color.withMultiplied(hue: 1.019, saturation: 0.867, brightness: 0.965)) case .nightAccent: - defaultColor = self.state.accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18) + defaultColor = HSBColor(color: self.state.accentColor.color.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18)) default: break } @@ -658,23 +667,19 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate if let defaultMessagesColor = self.state.defaultMessagesColor { defaultColor = defaultMessagesColor } else if let themeReference = self.mode.themeReference, case let .builtin(theme) = themeReference, theme == .nightAccent { - defaultColor = self.state.accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59) + defaultColor = HSBColor(color: self.state.accentColor.color.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59)) } else { defaultColor = self.state.accentColor } - if let messagesColors = self.state.messagesColors { - if let second = messagesColors.1 { - colors = [messagesColors.0.rgb, second.rgb] - } else { - colors = [messagesColors.0.rgb] - } + if !self.state.messagesColors.isEmpty { + colors = self.state.messagesColors } else { colors = [] } } if colors.isEmpty, let defaultColor = defaultColor { - colors = [defaultColor.rgb] + colors = [defaultColor] } let maximumNumberOfColors: Int @@ -684,7 +689,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate case .background: maximumNumberOfColors = 4 case .messages: - maximumNumberOfColors = 2 + maximumNumberOfColors = 4 default: maximumNumberOfColors = 1 } @@ -704,6 +709,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate needsLayout = true } + self.colorsButtonNode.colors = state.section == .messages ? state.messagesColors.map { $0.color } : state.backgroundColors.map { $0.color } + if previousState.colorPanelCollapsed != self.state.colorPanelCollapsed { animationCurve = .spring animationDuration = 0.45 @@ -746,12 +753,33 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate if (previousState.backgroundColors.count >= 2) != (self.state.backgroundColors.count >= 2) { needsLayout = true } + + if previousState.messagesColors.count != self.state.messagesColors.count { + needsLayout = true + } + + if previousState.animateMessageColors != self.state.animateMessageColors { + needsLayout = true + } + + if previousState.selectedColor != self.state.selectedColor { + needsLayout = true + } - if previousState.backgroundColors.count != self.state.backgroundColors.count { - if self.state.backgroundColors.count <= 2 { - self.playButtonNode.setImage(self.playButtonRotateImage, for: []) - } else { - self.playButtonNode.setImage(self.playButtonPlayImage, for: []) + if let section = self.state.section { + switch section { + case .background: + if previousState.backgroundColors.count != self.state.backgroundColors.count { + if self.state.backgroundColors.count <= 2 { + self.playButtonNode.setImage(self.playButtonRotateImage, for: []) + } else { + self.playButtonNode.setImage(self.playButtonPlayImage, for: []) + } + } + case .accent: + break + case .messages: + self.playButtonNode.setImage(self.playButtonPlayImage, for: []) } } @@ -771,6 +799,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate updated.section = section updated.displayPatternPanel = false updated.colorPanelCollapsed = false + updated.selectedColor = 0 return updated }, animated: true) } @@ -900,22 +929,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } strongSelf.updateState({ state in var state = state - if state.section == .background { - state.colorPanelCollapsed = true - state.displayPatternPanel = false - } + state.colorPanelCollapsed = true + state.displayPatternPanel = false return state }, animated: true) - /*if message.flags.contains(.Incoming) { - self?.updateSection(.accent) - self?.requestSectionUpdate?(.accent) - } else { - self?.updateSection(.messages) - self?.requestSectionUpdate?(.messages) - }*/ - }, clickThroughMessage: { [weak self] in - self?.updateSection(.background) - self?.requestSectionUpdate?(.background) + }, clickThroughMessage: { }, backgroundNode: self.backgroundNode) return item } @@ -924,6 +942,18 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate if let messageNodes = self.messageNodes { for i in 0 ..< items.count { let itemNode = messageNodes[i] + + if self.crossfadeBubbles { + if let snapshot = itemNode.view.snapshotView(afterScreenUpdates: false) { + snapshot.frame = itemNode.bounds + itemNode.view.addSubview(snapshot) + + snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshot] _ in + snapshot?.removeFromSuperview() + }) + } + } + items[i].updateNode(async: { $0() }, node: { return itemNode }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in @@ -936,6 +966,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate apply(ListViewItemApply(isOnScreen: true)) }) } + self.crossfadeBubbles = false } else { var messageNodes: [ListViewItemNode] = [] for i in 0 ..< items.count { @@ -944,22 +975,30 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate itemNode = node apply().1(ListViewItemApply(isOnScreen: true)) }) - //itemNode!.subnodeTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0) messageNodes.append(itemNode!) self.messagesContainerNode.addSubnode(itemNode!) } self.messageNodes = messageNodes } + let toolbarHeight = 49.0 + layout.intrinsicInsets.bottom + var relativeOffset: CGFloat = 0.0 + if !self.state.colorPanelCollapsed && self.state.section == .messages { + relativeOffset = (CGFloat(self.state.selectedColor) / CGFloat(max(1, self.state.messagesColors.count))) * (self.colorPanelNode.frame.height + toolbarHeight + 144.0) * -1.0 + } + + let containerSize = CGSize(width: layout.size.width, height: layout.size.height - toolbarHeight - 44.0) var bottomOffset: CGFloat = 9.0 + bottomInset if let messageNodes = self.messageNodes { for itemNode in messageNodes { let previousFrame = itemNode.frame transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: itemNode.frame.size)) bottomOffset += itemNode.frame.height - itemNode.updateFrame(itemNode.frame, within: layout.size) + + let relativeFrame = itemNode.frame.offsetBy(dx: 0.0, dy: relativeOffset) + itemNode.updateAbsoluteRect(relativeFrame, within: containerSize) if case let .animated(duration, curve) = transition { - itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -itemNode.frame.minY + previousFrame.minY), animationCurve: curve, duration: duration) + itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -relativeFrame.minY + previousFrame.minY), animationCurve: curve, duration: duration) } } } @@ -970,7 +1009,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate headerItem.updateNode(dateHeaderNode, previous: nil, next: headerItem) } else { dateHeaderNode = headerItem.node(synchronousLoad: true) - //dateHeaderNode.subnodeTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0) self.messagesContainerNode.addSubnode(dateHeaderNode) self.dateHeaderNode = dateHeaderNode } @@ -1042,9 +1080,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate transition.updateBounds(node: self.backgroundWrapperNode, bounds: CGRect(origin: CGPoint(), size: layout.size)) - let displayOptionButtons = self.state.section == .background - var messagesBottomInset: CGFloat = bottomInset + let displayOptionButtons = self.state.section == .background || self.state.section == .messages + var messagesBottomInset: CGFloat = bottomInset if displayOptionButtons { messagesBottomInset += 56.0 } else if chatListPreviewAvailable { @@ -1070,7 +1108,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let maxButtonWidth = max(patternButtonSize.width, colorsButtonSize.width) let buttonSize = CGSize(width: maxButtonWidth, height: 30.0) - let patternAlpha: CGFloat = displayOptionButtons ? 1.0 : 0.0 + var patternAlpha: CGFloat = displayOptionButtons ? 1.0 : 0.0 let colorsAlpha: CGFloat = displayOptionButtons ? 1.0 : 0.0 let patternFrame: CGRect @@ -1083,15 +1121,32 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let playFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - playButtonSize.width) / 2.0), y: layout.size.height - bottomInset - 44.0 - buttonsVerticalOffset + floor((buttonSize.height - playButtonSize.height) / 2.0)), size: playButtonSize) let playAlpha: CGFloat - if self.state.backgroundColors.count >= 2 { - playAlpha = displayOptionButtons ? 1.0 : 0.0 - centerDistance += playButtonSize.width + var centerButtons = false + if self.state.section == .background { + if self.state.backgroundColors.count >= 2 { + playAlpha = displayOptionButtons ? 1.0 : 0.0 + centerDistance += playButtonSize.width + } else { + playAlpha = 0.0 + } + } else if self.state.section == .messages { + if self.state.messagesColors.count >= 3 { + patternAlpha = 1.0 + playAlpha = displayOptionButtons && self.state.animateMessageColors ? 1.0 : 0.0 + if self.state.animateMessageColors { + centerDistance += playButtonSize.width + } + } else { + patternAlpha = 0.0 + playAlpha = 0.0 + centerButtons = true + } } else { playAlpha = 0.0 } - - patternFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width * 2.0 - centerDistance) / 2.0), y: layout.size.height - bottomInset - 44.0 - buttonsVerticalOffset), size: buttonSize) - colorsFrame = CGRect(origin: CGPoint(x: patternFrame.maxX + centerDistance, y: layout.size.height - bottomInset - 44.0 - buttonsVerticalOffset), size: buttonSize) + + patternFrame = CGRect(origin: CGPoint(x: centerButtons ? floor((layout.size.width - buttonSize.width) / 2.0) : floor((layout.size.width - buttonSize.width * 2.0 - centerDistance) / 2.0), y: layout.size.height - bottomInset - 44.0 - buttonsVerticalOffset), size: buttonSize) + colorsFrame = CGRect(origin: CGPoint(x: centerButtons ? floor((layout.size.width - buttonSize.width) / 2.0) : patternFrame.maxX + centerDistance, y: layout.size.height - bottomInset - 44.0 - buttonsVerticalOffset), size: buttonSize) transition.updateFrame(node: self.patternButtonNode, frame: patternFrame) transition.updateAlpha(node: self.patternButtonNode, alpha: patternAlpha) @@ -1105,35 +1160,22 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } @objc private func togglePattern() { - self.view.endEditing(true) - - let wallpaper = self.state.previousPatternWallpaper ?? self.patternPanelNode.wallpapers.first - let backgroundColors = self.currentBackgroundColors - - var appeared = false - self.updateState({ current in - var updated = current - if !updated.displayPatternPanel { - updated.colorPanelCollapsed = false - updated.displayPatternPanel = true - if current.patternWallpaper == nil, let wallpaper = wallpaper { - updated.patternWallpaper = wallpaper - if updated.backgroundColors.isEmpty { - if let backgroundColors = backgroundColors { - updated.backgroundColors = backgroundColors.0 - } else { - updated.backgroundColors = [] - } - } - appeared = true - } - } else { - updated.colorPanelCollapsed = true - if updated.patternWallpaper != nil { - updated.previousPatternWallpaper = updated.patternWallpaper - updated.patternWallpaper = nil - updated.displayPatternPanel = false - } else { + if self.state.section == .messages { + self.updateState({ current in + var updated = current + updated.animateMessageColors = !current.animateMessageColors + return updated + }, animated: true) + } else { + self.view.endEditing(true) + + let wallpaper = self.state.previousPatternWallpaper ?? self.patternPanelNode.wallpapers.first + let backgroundColors = self.currentBackgroundColors + + var appeared = false + self.updateState({ current in + var updated = current + if !updated.displayPatternPanel { updated.colorPanelCollapsed = false updated.displayPatternPanel = true if current.patternWallpaper == nil, let wallpaper = wallpaper { @@ -1145,15 +1187,36 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate updated.backgroundColors = [] } } - appeared = true + } + appeared = true + } else { + updated.colorPanelCollapsed = true + if updated.patternWallpaper != nil { + updated.previousPatternWallpaper = updated.patternWallpaper + updated.patternWallpaper = nil + updated.displayPatternPanel = false + } else { + updated.colorPanelCollapsed = false + updated.displayPatternPanel = true + if current.patternWallpaper == nil, let wallpaper = wallpaper { + updated.patternWallpaper = wallpaper + if updated.backgroundColors.isEmpty { + if let backgroundColors = backgroundColors { + updated.backgroundColors = backgroundColors.0 + } else { + updated.backgroundColors = [] + } + } + appeared = true + } } } + return updated + }, animated: true) + + if appeared { + self.patternPanelNode.didAppear(initialWallpaper: wallpaper, intensity: self.state.patternIntensity) } - return updated - }, animated: true) - - if appeared { - self.patternPanelNode.didAppear(initialWallpaper: wallpaper, intensity: self.state.patternIntensity) } } @@ -1176,12 +1239,12 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } @objc private func playPressed() { - if self.state.backgroundColors.count >= 3 { + if self.state.backgroundColors.count >= 3 || self.state.messagesColors.count >= 3 { self.backgroundNode.animateEvent(transition: .animated(duration: 0.5, curve: .spring)) } else { self.updateState({ state in var state = state - state.rotation = (state.rotation + 90) % 360 + state.rotation = (state.rotation + 45) % 360 return state }, animated: true) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift index 4fce8c0eb4..2f204a32a5 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift @@ -10,37 +10,37 @@ private func patternWallpaper(data: BuiltinWallpaperData, colors: [UInt32], inte var dayClassicColorPresets: [PresentationThemeAccentColor] = [ // Pink with Blue - PresentationThemeAccentColor(index: 106, baseColor: .preset, accentColor: 0xfff55783, bubbleColors: (0xffd6f5ff, 0xffc9fdfe), wallpaper: patternWallpaper(data: .default, colors: [0x8dc0eb, 0xb9d1ea, 0xc6b1ef, 0xebd7ef], intensity: 50, rotation: nil)), + PresentationThemeAccentColor(index: 106, baseColor: .preset, accentColor: 0xfff55783, bubbleColors: [0xffd6f5ff, 0xffc9fdfe], wallpaper: patternWallpaper(data: .default, colors: [0x8dc0eb, 0xb9d1ea, 0xc6b1ef, 0xebd7ef], intensity: 50, rotation: nil)), // Pink with Gold - PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0xFFFF5FA9, bubbleColors: (0xFFFFF4D7, nil), wallpaper: patternWallpaper(data: .variant12, colors: [0xeaa36e, 0xf0e486, 0xf29ebf, 0xe8c06e], intensity: 50, rotation: nil)), + PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0xFFFF5FA9, bubbleColors: [0xFFFFF4D7], wallpaper: patternWallpaper(data: .variant12, colors: [0xeaa36e, 0xf0e486, 0xf29ebf, 0xe8c06e], intensity: 50, rotation: nil)), // Green - PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xFF5A9E29, bubbleColors: (0xffFFF8DF, nil), wallpaper: patternWallpaper(data: .variant13, colors: [0x7fc289, 0xe4d573, 0xafd677, 0xf0c07a], intensity: 50, rotation: nil)), + PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xFF5A9E29, bubbleColors: [0xffFFF8DF], wallpaper: patternWallpaper(data: .variant13, colors: [0x7fc289, 0xe4d573, 0xafd677, 0xf0c07a], intensity: 50, rotation: nil)), // Purple - PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0xFF7E5FE5, bubbleColors: (0xFFF5e2FF, nil), wallpaper: patternWallpaper(data: .variant14, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: 50, rotation: nil)), + PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0xFF7E5FE5, bubbleColors: [0xFFF5e2FF], wallpaper: patternWallpaper(data: .variant14, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: 50, rotation: nil)), // Light Blue - PresentationThemeAccentColor(index: 107, baseColor: .preset, accentColor: 0xFF2CB9ED, bubbleColors: (0xFFADF7B5, 0xFFFCFF8B), wallpaper: patternWallpaper(data: .variant3, colors: [0x1a2e1a, 0x47623c, 0x222e24, 0x314429], intensity: 50, rotation: nil)), + PresentationThemeAccentColor(index: 107, baseColor: .preset, accentColor: 0xFF2CB9ED, bubbleColors: [0xFFADF7B5, 0xFFFCFF8B], wallpaper: patternWallpaper(data: .variant3, colors: [0x1a2e1a, 0x47623c, 0x222e24, 0x314429], intensity: 50, rotation: nil)), // Mint - PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xFF199972, bubbleColors: (0xFFFFFEC7, nil), wallpaper: patternWallpaper(data: .variant3, colors: [0xdceb92, 0x8fe1d6, 0x67a3f2, 0x85d685], intensity: 50, rotation: nil)), + PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xFF199972, bubbleColors: [0xFFFFFEC7], wallpaper: patternWallpaper(data: .variant3, colors: [0xdceb92, 0x8fe1d6, 0x67a3f2, 0x85d685], intensity: 50, rotation: nil)), // Pink with Green - PresentationThemeAccentColor(index: 105, baseColor: .preset, accentColor: 0xFFDA90D9, bubbleColors: (0xFF94FFF9, 0xFFCCFFC7), wallpaper: patternWallpaper(data: .variant9, colors: [0xffc3b2, 0xe2c0ff, 0xffe7b2], intensity: 50, rotation: nil)) + PresentationThemeAccentColor(index: 105, baseColor: .preset, accentColor: 0xFFDA90D9, bubbleColors: [0xFF94FFF9, 0xFFCCFFC7], wallpaper: patternWallpaper(data: .variant9, colors: [0xffc3b2, 0xe2c0ff, 0xffe7b2], intensity: 50, rotation: nil)) ] var dayColorPresets: [PresentationThemeAccentColor] = [ - PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: (0x007aff, 0xff53f4), wallpaper: nil), - PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: (0xaee946, 0x00b09b), wallpaper: nil), - PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: (0xf9db00, 0xd33213), wallpaper: nil), - PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: (0xea8ced, 0x00c2ed), wallpaper: nil) + PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: [0x007aff, 0xff53f4], wallpaper: nil), + PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: [0xaee946, 0x00b09b], wallpaper: nil), + PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: nil), + PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: nil) ] var nightColorPresets: [PresentationThemeAccentColor] = [ - PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: (0x007aff, 0xff53f4), wallpaper: patternWallpaper(data: .variant4, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)), - PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: (0xaee946, 0x00b09b), wallpaper: patternWallpaper(data: .variant9, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)), - PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: (0xf9db00, 0xd33213), wallpaper: patternWallpaper(data: .variant2, colors: [0xfec496, 0xdd6cb9, 0x962fbf, 0x4f5bd5], intensity: -40, rotation: nil)), - PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: (0xea8ced, 0x00c2ed), wallpaper: patternWallpaper(data: .variant6, colors: [0x8adbf2, 0x888dec, 0xe39fea, 0x679ced], intensity: -30, rotation: nil)) + PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: [0x007aff, 0xff53f4], wallpaper: patternWallpaper(data: .variant4, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)), + PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: [0xaee946, 0x00b09b], wallpaper: patternWallpaper(data: .variant9, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)), + PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: patternWallpaper(data: .variant2, colors: [0xfec496, 0xdd6cb9, 0x962fbf, 0x4f5bd5], intensity: -40, rotation: nil)), + PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: patternWallpaper(data: .variant6, colors: [0x8adbf2, 0x888dec, 0xe39fea, 0x679ced], intensity: -30, rotation: nil)) ] diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 6551db9c2c..8feb895a04 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -145,7 +145,7 @@ public final class ThemePreviewController: ViewController { |> deliverOnMainQueue).start(next: { [weak self] theme, presentationTheme in if let strongSelf = self, let theme = theme { let titleView = CounterContollerTitleView(theme: strongSelf.previewTheme) - titleView.title = CounterContollerTitle(title: themeName, counter: strongSelf.presentationData.strings.Theme_UsersCount(max(1, theme.installCount))) + titleView.title = CounterContollerTitle(title: themeName, counter: strongSelf.presentationData.strings.Theme_UsersCount(max(1, theme.installCount ?? 0))) strongSelf.navigationItem.titleView = titleView strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings)) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift index 515d129297..6a6bf55799 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift @@ -110,34 +110,28 @@ enum ThemeSettingsColorOption: Equatable { } } - var plainBubbleColors: (UIColor, UIColor)? { + var plainBubbleColors: [UInt32] { switch self { case let .accentColor(color): return color.plainBubbleColors case let .theme(reference): - if case let .cloud(theme) = reference, let settings = theme.theme.settings, let messageColors = settings.messageColors { - return (UIColor(argb: messageColors.top), UIColor(argb: messageColors.bottom)) + if case let .cloud(theme) = reference, let settings = theme.theme.settings, !settings.messageColors.isEmpty { + return settings.messageColors } else { - return nil + return [] } } } - var customBubbleColors: (UIColor, UIColor?)? { + var customBubbleColors: [UInt32] { switch self { case let .accentColor(color): return color.customBubbleColors case let .theme(reference): - if case let .cloud(theme) = reference, let settings = theme.theme.settings, let messageColors = settings.messageColors { - let topColor = UIColor(argb: messageColors.top) - let bottomColor = UIColor(argb: messageColors.bottom) - if topColor.argb != bottomColor.argb { - return (topColor, bottomColor) - } else { - return (topColor, nil) - } + if case let .cloud(theme) = reference, let settings = theme.theme.settings, !settings.messageColors.isEmpty { + return settings.messageColors } else { - return nil + return [] } } } @@ -381,9 +375,9 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode { var topColor: UIColor? var bottomColor: UIColor? - if let colors = item.color?.plainBubbleColors { - topColor = colors.0 - bottomColor = colors.1 + if let colors = item.color?.plainBubbleColors, !colors.isEmpty { + topColor = UIColor(rgb: colors[0]) + bottomColor = UIColor(rgb: colors.last ?? colors[0]) } else if case .builtin(.dayClassic) = item.themeReference { if let accentColor = item.color?.accentColor { let hsb = accentColor.hsb diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index 97cf234421..f1fa0b89e8 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -141,7 +141,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { currentBackgroundNode = WallpaperBackgroundNode(context: item.context) } currentBackgroundNode?.update(wallpaper: item.wallpaper) - currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners) + currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners) let insets: UIEdgeInsets let separatorHeight = UIScreenPixel @@ -225,25 +225,6 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { strongSelf.insertSubnode(currentBackgroundNode, at: 0) } - /*if let updatedBackgroundSignal = updatedBackgroundSignal { - strongSelf.disposable.set((updatedBackgroundSignal - |> deliverOnMainQueue).start(next: { [weak self] image in - if let strongSelf = self, let (image, final) = image, let backgroundNode = strongSelf.backgroundNode { - if final && !strongSelf.finalImage { - let tempLayer = CALayer() - tempLayer.frame = backgroundNode.bounds - tempLayer.contentsGravity = backgroundNode.layer.contentsGravity - tempLayer.contents = strongSelf.contents - strongSelf.layer.addSublayer(tempLayer) - tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in - tempLayer?.removeFromSuperlayer() - }) - } - backgroundNode.image = image - strongSelf.finalImage = final - } - })) - }*/ strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor @@ -280,13 +261,13 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) if let backgroundNode = strongSelf.backgroundNode { backgroundNode.frame = backgroundFrame.insetBy(dx: 0.0, dy: -100.0) backgroundNode.update(wallpaper: item.wallpaper) - backgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners) + backgroundNode.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners) backgroundNode.updateLayout(size: backgroundNode.bounds.size, transition: .immediate) } strongSelf.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 6c082bd9ff..025604f09a 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -647,7 +647,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The if let wallpaper = wallpaper { effectiveWallpaper = wallpaper } else { - let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors, wallpaper: accentColor?.wallpaper) + let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors ?? [], wallpaper: accentColor?.wallpaper) effectiveWallpaper = theme?.chat.defaultWallpaper ?? .builtin(WallpaperSettings()) } return (accentColor, effectiveWallpaper) @@ -669,7 +669,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationTheme?, TelegramWallpaper?), NoError> in return chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.sharedContext.accountManager.mediaBox) |> map { serviceBackgroundColor in - return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor), wallpaper) + return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: accentColor?.color, bubbleColors: accentColor?.customBubbleColors ?? [], serviceBackgroundColor: serviceBackgroundColor), wallpaper) } } |> deliverOnMainQueue).start(next: { theme, wallpaper in @@ -854,7 +854,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The default: break } - theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors, wallpaper: accentColor?.wallpaper, baseColor: baseColor) + theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors ?? [], wallpaper: accentColor?.wallpaper, baseColor: baseColor) } effectiveWallpaper = theme?.chat.defaultWallpaper ?? .builtin(WallpaperSettings()) } @@ -880,7 +880,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The if let accentColor = accentColor, case let .theme(themeReference) = accentColor { return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: themeReference, serviceBackgroundColor: serviceBackgroundColor), effectiveThemeReference, wallpaper) } else { - return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors, serviceBackgroundColor: serviceBackgroundColor), effectiveThemeReference, wallpaper) + return (makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: generalThemeReference, accentColor: accentColor?.accentColor, bubbleColors: accentColor?.customBubbleColors ?? [], serviceBackgroundColor: serviceBackgroundColor), effectiveThemeReference, wallpaper) } } |> mapToSignal { theme, reference, wallpaper in diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift index 65aa51cdb6..68407bebc6 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperColorPanelNode.swift @@ -312,7 +312,7 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { struct WallpaperColorPanelNodeState: Equatable { var selection: Int? - var colors: [UInt32] + var colors: [HSBColor] var maximumNumberOfColors: Int var rotateAvailable: Bool var rotation: Int32 @@ -390,7 +390,7 @@ final class WallpaperColorPanelNode: ASDisplayNode { private var sampleItemNodes: [ColorSampleItemNode] = [] private let multiColorFieldNode: ColorInputFieldNode - var colorsChanged: (([UInt32], Bool) -> Void)? + var colorsChanged: (([HSBColor], Int, Bool) -> Void)? var colorSelected: (() -> Void)? var rotate: (() -> Void)? @@ -456,7 +456,7 @@ final class WallpaperColorPanelNode: ASDisplayNode { var updated = current updated.preview = !ended if let index = strongSelf.state.selection { - updated.colors[index] = color.rgb + updated.colors[index] = HSBColor(color: color) } return updated }) @@ -486,7 +486,7 @@ final class WallpaperColorPanelNode: ASDisplayNode { var updated = current updated.preview = true if let index = strongSelf.state.selection { - updated.colors[index] = color.rgb + updated.colors[index] = color } return updated }, updateLayout: false) @@ -498,7 +498,7 @@ final class WallpaperColorPanelNode: ASDisplayNode { var updated = current updated.preview = false if let index = strongSelf.state.selection { - updated.colors[index] = color.rgb + updated.colors[index] = color } return updated }, updateLayout: false) @@ -518,6 +518,7 @@ final class WallpaperColorPanelNode: ASDisplayNode { var updateLayout = updateLayout let previousColors = self.state.colors let previousPreview = self.state.preview + let previousSelection = self.state.selection self.state = f(self.state) let colorWasRemovable = self.multiColorFieldNode.isRemovable @@ -528,7 +529,7 @@ final class WallpaperColorPanelNode: ASDisplayNode { if let index = self.state.selection { if self.state.colors.count > index { - self.colorPickerNode.color = UIColor(rgb: self.state.colors[index]) + self.colorPickerNode.color = self.state.colors[index] } } @@ -536,20 +537,20 @@ final class WallpaperColorPanelNode: ASDisplayNode { self.updateLayout(size: size, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate) } - if let index = state.selection { + if let index = self.state.selection { if self.state.colors.count > index { - self.multiColorFieldNode.setColor(UIColor(rgb: self.state.colors[index]), update: false) + self.multiColorFieldNode.setColor(self.state.colors[index].color, update: false) } } - for i in 0 ..< state.colors.count { + for i in 0 ..< self.state.colors.count { if i < self.sampleItemNodes.count { - self.sampleItemNodes[i].update(size: self.sampleItemNodes[i].bounds.size, color: UIColor(rgb: state.colors[i]), isSelected: state.selection == i) + self.sampleItemNodes[i].update(size: self.sampleItemNodes[i].bounds.size, color: self.state.colors[i].color, isSelected: state.selection == i) } } - if self.state.colors != previousColors || self.state.preview != previousPreview { - self.colorsChanged?(self.state.colors, !self.state.preview) + if self.state.colors != previousColors || self.state.preview != previousPreview || self.state.selection != previousSelection { + self.colorsChanged?(self.state.colors, self.state.selection ?? 0, !self.state.preview) } } @@ -576,42 +577,12 @@ final class WallpaperColorPanelNode: ASDisplayNode { } let buttonSize = CGSize(width: 26.0, height: 26.0) - //let middleButtonFrame = CGRect(origin: CGPoint(x: self.state.secondColor != nil ? floor((size.width - 26.0) / 2.0) : (self.state.secondColorAvailable ? size.width - rightInsetWithButton + floor((rightInsetWithButton - buttonSize.width) / 2.0) : size.width + buttonOffset), y: floor((topPanelHeight - buttonSize.height) / 2.0)), size: buttonSize) - - //transition.updateFrame(node: self.rotateButton, frame: middleButtonFrame) - //transition.updateFrame(node: self.swapButton, frame: middleButtonFrame) - let canAddColors = self.state.colors.count < self.state.maximumNumberOfColors transition.updateFrame(node: self.addButton, frame: CGRect(origin: CGPoint(x: size.width - rightInset - buttonSize.width, y: floor((topPanelHeight - buttonSize.height) / 2.0)), size: buttonSize)) transition.updateAlpha(node: self.addButton, alpha: canAddColors ? 1.0 : 0.0) transition.updateSublayerTransformScale(node: self.addButton, scale: canAddColors ? 1.0 : 0.1) - /*let rotateButtonAlpha: CGFloat - let swapButtonAlpha: CGFloat - let addButtonAlpha: CGFloat - if let _ = self.state.secondColor { - if self.state.rotateAvailable { - rotateButtonAlpha = 1.0 - swapButtonAlpha = 0.0 - } else { - rotateButtonAlpha = 0.0 - swapButtonAlpha = 1.0 - } - addButtonAlpha = 0.0 - } else { - swapButtonAlpha = 0.0 - rotateButtonAlpha = 0.0 - if self.state.secondColorAvailable { - addButtonAlpha = 1.0 - } else { - addButtonAlpha = 0.0 - } - } - transition.updateAlpha(node: self.rotateButton, alpha: rotateButtonAlpha) - transition.updateAlpha(node: self.swapButton, alpha: swapButtonAlpha) - transition.updateAlpha(node: self.addButton, alpha: addButtonAlpha)*/ - func degreesToRadians(_ degrees: CGFloat) -> CGFloat { var degrees = degrees if degrees >= 270.0 { @@ -658,7 +629,7 @@ final class WallpaperColorPanelNode: ASDisplayNode { } itemNode.frame = CGRect(origin: CGPoint(x: nextSampleX, y: (topPanelHeight - sampleItemSize) / 2.0), size: CGSize(width: sampleItemSize, height: sampleItemSize)) nextSampleX += sampleItemSize - itemNode.update(size: itemNode.bounds.size, color: UIColor(rgb: self.state.colors[i]), isSelected: self.state.selection == i) + itemNode.update(size: itemNode.bounds.size, color: self.state.colors[i].color, isSelected: self.state.selection == i) if animateIn { transition.animateTransformScale(node: itemNode, from: 0.1) @@ -722,9 +693,9 @@ final class WallpaperColorPanelNode: ASDisplayNode { var current = current if current.colors.count < current.maximumNumberOfColors { if current.colors.isEmpty { - current.colors.append(0xffffff) + current.colors.append(HSBColor(rgb: 0xffffff)) } else if current.simpleGradientGeneration { - var hsb = UIColor(rgb: current.colors[0]).hsb + var hsb = current.colors[0].values if hsb.1 > 0.5 { hsb.1 -= 0.15 } else { @@ -735,7 +706,7 @@ final class WallpaperColorPanelNode: ASDisplayNode { } else { hsb.0 += 0.05 } - current.colors.append(UIColor(hue: hsb.0, saturation: hsb.1, brightness: hsb.2, alpha: 1.0).rgb) + current.colors.append(HSBColor(values: hsb)) } else { current.colors.append(current.colors[current.colors.count - 1]) } @@ -743,38 +714,6 @@ final class WallpaperColorPanelNode: ASDisplayNode { } return current }) - - /*self.firstColorFieldNode.setSkipEndEditingIfNeeded() - - self.updateState({ current in - var updated = current - updated.selection = .index(1) - - let firstColor = current.firstColor ?? current.defaultColor - if let color = firstColor { - updated.firstColor = color - - let secondColor: UIColor - if updated.simpleGradientGeneration { - var hsb = color.hsb - if hsb.1 > 0.5 { - hsb.1 -= 0.15 - } else { - hsb.1 += 0.15 - } - if hsb.0 > 0.5 { - hsb.0 -= 0.05 - } else { - hsb.0 += 0.05 - } - updated.secondColor = UIColor(hue: hsb.0, saturation: hsb.1, brightness: hsb.2, alpha: 1.0) - } else { - updated.secondColor = generateGradientColors(color: color).1 - } - } - - return updated - })*/ } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift index 48b935af57..50e51a2aef 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperColorPickerNode.swift @@ -89,11 +89,10 @@ private final class HSBParameter: NSObject { } private final class WallpaperColorKnobNode: ASDisplayNode { - var hsb: (CGFloat, CGFloat, CGFloat) = (0.0, 0.0, 1.0) { + var color: HSBColor = HSBColor(hue: 0.0, saturation: 0.0, brightness: 1.0) { didSet { - if self.hsb != oldValue { - let color = UIColor(hue: hsb.0, saturation: hsb.1, brightness: hsb.2, alpha: 1.0) - self.colorNode.backgroundColor = color + if self.color != oldValue { + self.colorNode.backgroundColor = self.color.color } } } @@ -166,6 +165,64 @@ private final class WallpaperColorHueSaturationNode: ASDisplayNode { context.setFillColor(UIColor(rgb: 0x000000, alpha: 1.0 - parameters.value).cgColor) context.fill(bounds) } + + var tap: ((CGPoint) -> Void)? + var panBegan: ((CGPoint) -> Void)? + var panChanged: ((CGPoint, Bool) -> Void)? + + var initialTouchLocation: CGPoint? + var touchMoved = false + var previousTouchLocation: CGPoint? + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + + if let touchLocation = touches.first?.location(in: self.view) { + self.touchMoved = false + self.initialTouchLocation = touchLocation + self.previousTouchLocation = nil + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) + + if let touchLocation = touches.first?.location(in: self.view), let initialLocation = self.initialTouchLocation { + let dX = touchLocation.x - initialLocation.x + let dY = touchLocation.y - initialLocation.y + if !self.touchMoved && dX * dX + dY * dY > 3.0 { + self.touchMoved = true + self.panBegan?(touchLocation) + self.previousTouchLocation = touchLocation + } else if let previousTouchLocation = self.previousTouchLocation { + let dX = touchLocation.x - previousTouchLocation.x + let dY = touchLocation.y - previousTouchLocation.y + let translation = CGPoint(x: dX, y: dY) + + self.panChanged?(translation, false) + self.previousTouchLocation = touchLocation + } + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + + if self.touchMoved { + if let touchLocation = touches.first?.location(in: self.view), let previousTouchLocation = self.previousTouchLocation { + let dX = touchLocation.x - previousTouchLocation.x + let dY = touchLocation.y - previousTouchLocation.y + let translation = CGPoint(x: dX, y: dY) + + self.panChanged?(translation, true) + } + } else if let touchLocation = self.initialTouchLocation { + self.tap?(touchLocation) + } + } + + override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + } } private final class WallpaperColorBrightnessNode: ASDisplayNode { @@ -209,6 +266,54 @@ private final class WallpaperColorBrightnessNode: ASDisplayNode { } } +struct HSBColor: Equatable { + static func == (lhs: HSBColor, rhs: HSBColor) -> Bool { + return lhs.values.h == rhs.values.h && lhs.values.s == rhs.values.s && lhs.values.b == rhs.values.b + } + + let values: (h: CGFloat, s: CGFloat, b: CGFloat) + let backingColor: UIColor + + var hue: CGFloat { + return self.values.h + } + + var saturation: CGFloat { + return self.values.s + } + + var brightness: CGFloat { + return self.values.b + } + + var rgb: UInt32 { + return self.color.argb + } + + init(values: (h: CGFloat, s: CGFloat, b: CGFloat)) { + self.values = values + self.backingColor = UIColor(hue: values.h, saturation: values.s, brightness: values.b, alpha: 1.0) + } + + init(hue: CGFloat, saturation: CGFloat, brightness: CGFloat) { + self.values = (h: hue, s: saturation, b: brightness) + self.backingColor = UIColor(hue: self.values.h, saturation: self.values.s, brightness: self.values.b, alpha: 1.0) + } + + init(color: UIColor) { + self.values = color.hsb + self.backingColor = color + } + + init(rgb: UInt32) { + self.init(color: UIColor(rgb: rgb)) + } + + var color: UIColor { + return self.backingColor + } +} + final class WallpaperColorPickerNode: ASDisplayNode { private let brightnessNode: WallpaperColorBrightnessNode private let brightnessKnobNode: ASImageNode @@ -217,21 +322,16 @@ final class WallpaperColorPickerNode: ASDisplayNode { private var validLayout: CGSize? - var colorHsb: (CGFloat, CGFloat, CGFloat) = (0.0, 1.0, 1.0) - var color: UIColor { - get { - return UIColor(hue: self.colorHsb.0, saturation: self.colorHsb.1, brightness: self.colorHsb.2, alpha: 1.0) - } - set { - let newHsb = newValue.hsb - if newHsb != self.colorHsb { - self.colorHsb = newHsb + var color: HSBColor = HSBColor(hue: 0.0, saturation: 1.0, brightness: 1.0) { + didSet { + if self.color != oldValue { self.update() } } } - var colorChanged: ((UIColor) -> Void)? - var colorChangeEnded: ((UIColor) -> Void)? + + var colorChanged: ((HSBColor) -> Void)? + var colorChangeEnded: ((HSBColor) -> Void)? init(strings: PresentationStrings) { self.brightnessNode = WallpaperColorBrightnessNode() @@ -253,16 +353,79 @@ final class WallpaperColorPickerNode: ASDisplayNode { self.addSubnode(self.colorKnobNode) self.update() + + self.colorNode.tap = { [weak self] location in + guard let strongSelf = self, let size = strongSelf.validLayout else { + return + } + + let colorHeight = size.height - 66.0 + + let newHue = max(0.0, min(1.0, location.x / size.width)) + let newSaturation = max(0.0, min(1.0, (1.0 - location.y / colorHeight))) + strongSelf.color = HSBColor(hue: newHue, saturation: newSaturation, brightness: strongSelf.color.brightness) + + strongSelf.updateKnobLayout(size: size, panningColor: false, transition: .immediate) + + strongSelf.update() + strongSelf.colorChangeEnded?(strongSelf.color) + } + + self.colorNode.panBegan = { [weak self] location in + guard let strongSelf = self, let size = strongSelf.validLayout else { + return + } + + let previousColor = strongSelf.color + + let colorHeight = size.height - 66.0 + + let newHue = max(0.0, min(1.0, location.x / size.width)) + let newSaturation = max(0.0, min(1.0, (1.0 - location.y / colorHeight))) + strongSelf.color = HSBColor(hue: newHue, saturation: newSaturation, brightness: strongSelf.color.brightness) + + strongSelf.updateKnobLayout(size: size, panningColor: true, transition: .immediate) + + if strongSelf.color != previousColor { + strongSelf.colorChanged?(strongSelf.color) + } + } + + self.colorNode.panChanged = { [weak self] translation, ended in + guard let strongSelf = self, let size = strongSelf.validLayout else { + return + } + + let previousColor = strongSelf.color + + let colorHeight = size.height - 66.0 + + let newHue = max(0.0, min(1.0, strongSelf.color.hue + translation.x / size.width)) + let newSaturation = max(0.0, min(1.0, strongSelf.color.saturation - translation.y / colorHeight)) + strongSelf.color = HSBColor(hue: newHue, saturation: newSaturation, brightness: strongSelf.color.brightness) + + if ended { + strongSelf.updateKnobLayout(size: size, panningColor: false, transition: .animated(duration: 0.3, curve: .easeInOut)) + } else { + strongSelf.updateKnobLayout(size: size, panningColor: true, transition: .immediate) + } + + if strongSelf.color != previousColor || ended { + strongSelf.update() + if ended { + strongSelf.colorChangeEnded?(strongSelf.color) + } else { + strongSelf.colorChanged?(strongSelf.color) + } + } + } } override func didLoad() { super.didLoad() - let colorPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(WallpaperColorPickerNode.colorPan)) - self.colorNode.view.addGestureRecognizer(colorPanRecognizer) - - let colorTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(WallpaperColorPickerNode.colorTap)) - self.colorNode.view.addGestureRecognizer(colorTapRecognizer) + self.view.disablesInteractiveTransitionGestureRecognizer = true + self.view.disablesInteractiveModalDismiss = true let brightnessPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(WallpaperColorPickerNode.brightnessPan)) self.brightnessNode.view.addGestureRecognizer(brightnessPanRecognizer) @@ -270,16 +433,16 @@ final class WallpaperColorPickerNode: ASDisplayNode { private func update() { self.backgroundColor = .white - self.colorNode.value = self.colorHsb.2 - self.brightnessNode.hsb = self.colorHsb - self.colorKnobNode.hsb = self.colorHsb + self.colorNode.value = self.color.brightness + self.brightnessNode.hsb = self.color.values + self.colorKnobNode.color = self.color } private func updateKnobLayout(size: CGSize, panningColor: Bool, transition: ContainedViewLayoutTransition) { let knobSize = CGSize(width: 45.0, height: 45.0) let colorHeight = size.height - 66.0 - var colorKnobFrame = CGRect(x: floorToScreenPixels(-knobSize.width / 2.0 + size.width * self.colorHsb.0), y: floorToScreenPixels(-knobSize.height / 2.0 + (colorHeight * (1.0 - self.colorHsb.1))), width: knobSize.width, height: knobSize.height) + var colorKnobFrame = CGRect(x: floorToScreenPixels(-knobSize.width / 2.0 + size.width * self.color.hue), y: floorToScreenPixels(-knobSize.height / 2.0 + (colorHeight * (1.0 - self.color.saturation))), width: knobSize.width, height: knobSize.height) var origin = colorKnobFrame.origin if !panningColor { origin = CGPoint(x: max(0.0, min(origin.x, size.width - knobSize.width)), y: max(0.0, min(origin.y, colorHeight - knobSize.height))) @@ -291,7 +454,7 @@ final class WallpaperColorPickerNode: ASDisplayNode { let inset: CGFloat = 15.0 let brightnessKnobSize = CGSize(width: 12.0, height: 55.0) - let brightnessKnobFrame = CGRect(x: inset - brightnessKnobSize.width / 2.0 + (size.width - inset * 2.0) * (1.0 - self.colorHsb.2), y: size.height - 65.0, width: brightnessKnobSize.width, height: brightnessKnobSize.height) + let brightnessKnobFrame = CGRect(x: inset - brightnessKnobSize.width / 2.0 + (size.width - inset * 2.0) * (1.0 - self.color.brightness), y: size.height - 65.0, width: brightnessKnobSize.width, height: brightnessKnobSize.height) transition.updateFrame(node: self.brightnessKnobNode, frame: brightnessKnobFrame) } @@ -307,72 +470,6 @@ final class WallpaperColorPickerNode: ASDisplayNode { self.updateKnobLayout(size: size, panningColor: false, transition: .immediate) } - @objc private func colorTap(_ recognizer: UITapGestureRecognizer) { - guard let size = self.validLayout, recognizer.state == .recognized else { - return - } - - let colorHeight = size.height - 66.0 - - let location = recognizer.location(in: recognizer.view) - let newHue = max(0.0, min(1.0, location.x / size.width)) - let newSaturation = max(0.0, min(1.0, (1.0 - location.y / colorHeight))) - self.colorHsb.0 = newHue - self.colorHsb.1 = newSaturation - - self.updateKnobLayout(size: size, panningColor: false, transition: .immediate) - - self.update() - self.colorChangeEnded?(self.color) - } - - @objc private func colorPan(_ recognizer: UIPanGestureRecognizer) { - guard let size = self.validLayout else { - return - } - - let previousColor = self.color - - let colorHeight = size.height - 66.0 - - let location = recognizer.location(in: recognizer.view) - let transition = recognizer.translation(in: recognizer.view) - if recognizer.state == .began { - let newHue = max(0.0, min(1.0, location.x / size.width)) - let newSaturation = max(0.0, min(1.0, (1.0 - location.y / colorHeight))) - self.colorHsb.0 = newHue - self.colorHsb.1 = newSaturation - } else { - let newHue = max(0.0, min(1.0, self.colorHsb.0 + transition.x / size.width)) - let newSaturation = max(0.0, min(1.0, self.colorHsb.1 - transition.y / (size.height - 66.0))) - self.colorHsb.0 = newHue - self.colorHsb.1 = newSaturation - } - - var ended = false - switch recognizer.state { - case .began: - self.updateKnobLayout(size: size, panningColor: true, transition: .immediate) - case .changed: - self.updateKnobLayout(size: size, panningColor: true, transition: .immediate) - recognizer.setTranslation(CGPoint(), in: recognizer.view) - case .ended: - self.updateKnobLayout(size: size, panningColor: false, transition: .animated(duration: 0.3, curve: .easeInOut)) - ended = true - default: - break - } - - if self.color != previousColor || ended { - self.update() - if ended { - self.colorChangeEnded?(self.color) - } else { - self.colorChanged?(self.color) - } - } - } - @objc private func brightnessPan(_ recognizer: UIPanGestureRecognizer) { guard let size = self.validLayout else { return @@ -382,9 +479,9 @@ final class WallpaperColorPickerNode: ASDisplayNode { let transition = recognizer.translation(in: recognizer.view) let brightnessWidth: CGFloat = size.width - 42.0 * 2.0 - let newValue = max(0.0, min(1.0, self.colorHsb.2 - transition.x / brightnessWidth)) - self.colorHsb.2 = newValue - + let newValue = max(0.0, min(1.0, self.color.brightness - transition.x / brightnessWidth)) + self.color = HSBColor(hue: self.color.hue, saturation: self.color.saturation, brightness: newValue) + var ended = false switch recognizer.state { case .changed: diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index d9e2e886ad..1706880995 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -631,11 +631,11 @@ public class WallpaperGalleryController: ViewController { strongSelf.patternInitialWallpaper = enabled ? initialWallpaper : nil switch initialWallpaper { case let .color(color): - strongSelf.patternPanelNode?.backgroundColors = ([color], nil, nil) + strongSelf.patternPanelNode?.backgroundColors = ([HSBColor(rgb: color)], nil, nil) case let .gradient(gradient): - strongSelf.patternPanelNode?.backgroundColors = (gradient.colors, gradient.settings.rotation, nil) + strongSelf.patternPanelNode?.backgroundColors = (gradient.colors.map { HSBColor(rgb: $0) }, gradient.settings.rotation, nil) case let .file(file) where file.isPattern: - strongSelf.patternPanelNode?.backgroundColors = (file.settings.colors, file.settings.rotation, file.settings.intensity) + strongSelf.patternPanelNode?.backgroundColors = (file.settings.colors.map { HSBColor(rgb: $0) }, file.settings.rotation, file.settings.intensity) default: break } @@ -678,7 +678,7 @@ public class WallpaperGalleryController: ViewController { strongSelf.colorsPanelNode?.updateState({ _ in return WallpaperColorPanelNodeState( selection: 0, - colors: colors.map(\.rgb), + colors: colors.map { HSBColor(color: $0) }, maximumNumberOfColors: 4, rotateAvailable: false, rotation: 0, @@ -863,7 +863,7 @@ public class WallpaperGalleryController: ViewController { break } - strongSelf.patternPanelNode?.backgroundColors = (colors, rotation, intensity) + strongSelf.patternPanelNode?.backgroundColors = (colors.map { HSBColor(rgb: $0) }, rotation, intensity) } } self.patternPanelNode = patternPanelNode @@ -880,7 +880,7 @@ public class WallpaperGalleryController: ViewController { currentColorsPanelNode = colorsPanelNode self.overlayNode?.insertSubnode(colorsPanelNode, belowSubnode: self.toolbarNode!) - colorsPanelNode.colorsChanged = { [weak self] colors, _ in + colorsPanelNode.colorsChanged = { [weak self] colors, _, _ in guard let strongSelf = self else { return } @@ -888,10 +888,10 @@ public class WallpaperGalleryController: ViewController { return } - var wallpaper: TelegramWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: colors, settings: WallpaperSettings(blur: false, motion: false, colors: [], intensity: nil, rotation: nil))) + var wallpaper: TelegramWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: colors.map { $0.rgb }, settings: WallpaperSettings(blur: false, motion: false, colors: [], intensity: nil, rotation: nil))) if case let .file(file) = currentWallpaper { - wallpaper = currentWallpaper.withUpdatedSettings(WallpaperSettings(blur: false, motion: false, colors: colors, intensity: file.settings.intensity, rotation: file.settings.rotation)) + wallpaper = currentWallpaper.withUpdatedSettings(WallpaperSettings(blur: false, motion: false, colors: colors.map { $0.rgb }, intensity: file.settings.intensity, rotation: file.settings.rotation)) } strongSelf.updateEntries(wallpaper: wallpaper) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index b0ebf964cb..51da46b485 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -63,8 +63,15 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { } } + var title: String { + didSet { + self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white) + } + } + init(title: String, value: WallpaperOptionButtonValue) { self._value = value + self.title = title self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.3)) self.backgroundNode.cornerRadius = 14.0 diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperPatternPanelNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperPatternPanelNode.swift index 40ef3d92ad..49f8145eda 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperPatternPanelNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperPatternPanelNode.swift @@ -200,7 +200,7 @@ final class WallpaperPatternPanelNode: ASDisplayNode { } } - var backgroundColors: ([UInt32], Int32?, Int32?)? = nil { + var backgroundColors: ([HSBColor], Int32?, Int32?)? = nil { didSet { var updated = false if oldValue?.0 != self.backgroundColors?.0 || oldValue?.1 != self.backgroundColors?.1 { @@ -329,7 +329,7 @@ final class WallpaperPatternPanelNode: ASDisplayNode { node.removeFromSupernode() } - let backgroundColors = self.backgroundColors ?? ([0xd6e2ee], nil, nil) + let backgroundColors = self.backgroundColors.flatMap { ($0.0.map({ $0.rgb }), $0.1, $0.2) } ?? ([0xd6e2ee], nil, nil) let intensity: Int32 = backgroundColors.2.flatMap { value in if value < 0 { return -80 diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index 11a86a6b54..b3ea2e1a71 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -194,7 +194,11 @@ private func usernameSetupControllerEntries(presentationData: PresentationData, switch error { case .startsWithDigit: statusText = presentationData.strings.Username_InvalidStartsWithNumber - case .startsWithUnderscore, .endsWithUnderscore, .invalidCharacters: + case .startsWithUnderscore: + statusText = presentationData.strings.Username_InvalidStartsWithUnderscore + case .endsWithUnderscore: + statusText = presentationData.strings.Username_InvalidEndsWithUnderscore + case .invalidCharacters: statusText = presentationData.strings.Username_InvalidCharacters case .tooShort: statusText = presentationData.strings.Username_InvalidTooShort diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 62c62c5be4..8535b77020 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -387,20 +387,22 @@ public final class ShareController: ViewController { break case let .image(representations): if case .saveToCameraRoll = preferredAction { - self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Preview_SaveToCameraRoll, action: { [weak self] in + self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Gallery_SaveImage, action: { [weak self] in self?.saveToCameraRoll(representations: representations) self?.actionCompleted?() }) } case let .media(mediaReference): var canSave = false + var isVideo = false if mediaReference.media is TelegramMediaImage { canSave = true - } else if mediaReference.media is TelegramMediaFile { + } else if let file = mediaReference.media as? TelegramMediaFile { canSave = true + isVideo = file.isVideo } if case .saveToCameraRoll = preferredAction, canSave { - self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Preview_SaveToCameraRoll, action: { [weak self] in + self.defaultAction = ShareControllerAction(title: isVideo ? self.presentationData.strings.Gallery_SaveVideo : self.presentationData.strings.Gallery_SaveImage, action: { [weak self] in self?.saveToCameraRoll(mediaReference: mediaReference) self?.actionCompleted?() }) diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 56fce91c05..04ff60d6e8 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -955,8 +955,6 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate } if let status = status { contentNode.state = .progress(status) - } else { - } }, completed: { [weak self] in guard let strongSelf = self, let contentNode = strongSelf.contentNode as? ShareLoadingContainerNode else { diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 2f63f71b6f..3a6fa3a677 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -5,10 +5,12 @@ import Display public final class SolidRoundedButtonTheme { public let backgroundColor: UIColor + public let gradientBackgroundColor: UIColor? public let foregroundColor: UIColor - public init(backgroundColor: UIColor, foregroundColor: UIColor) { + public init(backgroundColor: UIColor, gradientBackgroundColor: UIColor? = nil, foregroundColor: UIColor) { self.backgroundColor = backgroundColor + self.gradientBackgroundColor = gradientBackgroundColor self.foregroundColor = foregroundColor } } @@ -59,6 +61,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode { self.title = title self.buttonBackgroundNode = ASDisplayNode() + self.buttonBackgroundNode.clipsToBounds = true self.buttonBackgroundNode.backgroundColor = theme.backgroundColor self.buttonBackgroundNode.cornerRadius = cornerRadius if #available(iOS 13.0, *) { 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/StickerResources/Sources/StickerResources.swift b/submodules/StickerResources/Sources/StickerResources.swift index 3703f2a8c2..3a7710be48 100644 --- a/submodules/StickerResources/Sources/StickerResources.swift +++ b/submodules/StickerResources/Sources/StickerResources.swift @@ -373,6 +373,8 @@ public func chatMessageStickerPackThumbnail(postbox: Postbox, resource: MediaRes } } + addCorners(context, arguments: arguments) + return context } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 13859f9939..669d010e3e 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -11,12 +11,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) } dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } - dict[1509543498] = { return Api.ChatFull.parse_chatFull($0) } - dict[1517757976] = { return Api.ChatFull.parse_channelFull($0) } - dict[-591909213] = { return Api.PollResults.parse_pollResults($0) } - dict[-1070776313] = { return Api.ChatParticipant.parse_chatParticipant($0) } - dict[-462696732] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) } - dict[-1600962725] = { return Api.ChatParticipant.parse_chatParticipantAdmin($0) } + dict[1235264985] = { return Api.ChatFull.parse_chatFull($0) } + dict[793980732] = { return Api.ChatFull.parse_channelFull($0) } + dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) } + dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) } + dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) } + dict[-489233354] = { return Api.ChatParticipant.parse_chatParticipantAdmin($0) } dict[1567990072] = { return Api.updates.Difference.parse_differenceEmpty($0) } dict[16030880] = { return Api.updates.Difference.parse_difference($0) } dict[-1459938943] = { return Api.updates.Difference.parse_differenceSlice($0) } @@ -65,11 +65,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1715350371] = { return Api.JSONValue.parse_jsonObject($0) } dict[590459437] = { return Api.Photo.parse_photoEmpty($0) } dict[-82216347] = { return Api.Photo.parse_photo($0) } - dict[693512293] = { return Api.Chat.parse_chatEmpty($0) } - dict[1103884886] = { return Api.Chat.parse_chat($0) } - dict[1704108455] = { return Api.Chat.parse_chatForbidden($0) } - dict[-2107528095] = { return Api.Chat.parse_channel($0) } - dict[399807445] = { return Api.Chat.parse_channelForbidden($0) } + dict[-1683826688] = { return Api.Chat.parse_chatEmpty($0) } + dict[1004149726] = { return Api.Chat.parse_chat($0) } + dict[120753115] = { return Api.Chat.parse_chatForbidden($0) } + dict[-753232354] = { return Api.Chat.parse_channel($0) } + dict[681420594] = { return Api.Chat.parse_channelForbidden($0) } dict[1202287072] = { return Api.StatsURL.parse_statsURL($0) } dict[1516793212] = { return Api.ChatInvite.parse_chatInviteAlready($0) } dict[-540871282] = { return Api.ChatInvite.parse_chatInvite($0) } @@ -83,8 +83,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1678812626] = { return Api.StickerSetCovered.parse_stickerSetCovered($0) } dict[872932635] = { return Api.StickerSetCovered.parse_stickerSetMultiCovered($0) } dict[1189204285] = { return Api.RecentMeUrl.parse_recentMeUrlUnknown($0) } - dict[-1188296222] = { return Api.RecentMeUrl.parse_recentMeUrlUser($0) } - dict[-1294306862] = { return Api.RecentMeUrl.parse_recentMeUrlChat($0) } + dict[-1917045962] = { return Api.RecentMeUrl.parse_recentMeUrlUser($0) } + dict[-1608834311] = { return Api.RecentMeUrl.parse_recentMeUrlChat($0) } dict[-347535331] = { return Api.RecentMeUrl.parse_recentMeUrlChatInvite($0) } dict[-1140172836] = { return Api.RecentMeUrl.parse_recentMeUrlStickerSet($0) } dict[-797791052] = { return Api.RestrictionReason.parse_restrictionReason($0) } @@ -106,10 +106,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[483104362] = { return Api.RichText.parse_textPhone($0) } dict[136105807] = { return Api.RichText.parse_textImage($0) } dict[894777186] = { return Api.RichText.parse_textAnchor($0) } - dict[328899191] = { return Api.UserFull.parse_userFull($0) } + dict[-694681851] = { return Api.UserFull.parse_userFull($0) } dict[-292807034] = { return Api.InputChannel.parse_inputChannelEmpty($0) } - dict[-212145112] = { return Api.InputChannel.parse_inputChannel($0) } - dict[1536380829] = { return Api.InputChannel.parse_inputChannelFromMessage($0) } + dict[-1343524562] = { return Api.InputChannel.parse_inputChannel($0) } + dict[707290417] = { return Api.InputChannel.parse_inputChannelFromMessage($0) } dict[414687501] = { return Api.DcOption.parse_dcOption($0) } dict[997055186] = { return Api.PollAnswerVoters.parse_pollAnswerVoters($0) } dict[-1705233435] = { return Api.account.PasswordSettings.parse_passwordSettings($0) } @@ -121,7 +121,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-209337866] = { return Api.LangPackDifference.parse_langPackDifference($0) } dict[499236004] = { return Api.WallPaperSettings.parse_wallPaperSettings($0) } dict[-1519029347] = { return Api.EmojiURL.parse_emojiURL($0) } - dict[-682079097] = { return Api.StatsGroupTopAdmin.parse_statsGroupTopAdmin($0) } + dict[1611985938] = { return Api.StatsGroupTopAdmin.parse_statsGroupTopAdmin($0) } dict[-541588713] = { return Api.channels.ChannelParticipant.parse_channelParticipant($0) } dict[-1736378792] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordEmpty($0) } dict[-763367294] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordSRP($0) } @@ -151,9 +151,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1148485274] = { return Api.auth.Authorization.parse_authorizationSignUpRequired($0) } dict[-181407105] = { return Api.InputFile.parse_inputFile($0) } dict[-95482955] = { return Api.InputFile.parse_inputFileBig($0) } - dict[1498486562] = { return Api.Peer.parse_peerUser($0) } - dict[918946202] = { return Api.Peer.parse_peerChat($0) } - dict[-1566230754] = { return Api.Peer.parse_peerChannel($0) } + dict[-1649296275] = { return Api.Peer.parse_peerUser($0) } + dict[-1160714821] = { return Api.Peer.parse_peerChat($0) } + dict[-1109531342] = { return Api.Peer.parse_peerChannel($0) } dict[410107472] = { return Api.messages.ExportedChatInvite.parse_exportedChatInvite($0) } dict[572915951] = { return Api.messages.ExportedChatInvite.parse_exportedChatInviteReplaced($0) } dict[-1868808300] = { return Api.PaymentRequestedInfo.parse_paymentRequestedInfo($0) } @@ -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) } @@ -192,64 +193,64 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[522914557] = { return Api.Update.parse_updateNewMessage($0) } dict[1318109142] = { return Api.Update.parse_updateMessageID($0) } dict[-1576161051] = { return Api.Update.parse_updateDeleteMessages($0) } - dict[-1071741569] = { return Api.Update.parse_updateUserTyping($0) } - dict[-2092401936] = { return Api.Update.parse_updateChatUserTyping($0) } + dict[1548249383] = { return Api.Update.parse_updateUserTyping($0) } + dict[-2033525908] = { return Api.Update.parse_updateChatUserTyping($0) } dict[125178264] = { return Api.Update.parse_updateChatParticipants($0) } - dict[-440534818] = { return Api.Update.parse_updateUserStatus($0) } - dict[-1007549728] = { return Api.Update.parse_updateUserName($0) } - dict[-232290676] = { return Api.Update.parse_updateUserPhoto($0) } + dict[469489699] = { return Api.Update.parse_updateUserStatus($0) } + dict[-1489818765] = { return Api.Update.parse_updateUserName($0) } + dict[-1791935732] = { return Api.Update.parse_updateUserPhoto($0) } dict[314359194] = { return Api.Update.parse_updateNewEncryptedMessage($0) } dict[386986326] = { return Api.Update.parse_updateEncryptedChatTyping($0) } dict[-1264392051] = { return Api.Update.parse_updateEncryption($0) } dict[956179895] = { return Api.Update.parse_updateEncryptedMessagesRead($0) } - dict[1037718609] = { return Api.Update.parse_updateChatParticipantAdd($0) } - dict[-483443337] = { return Api.Update.parse_updateChatParticipantDelete($0) } + dict[-364179876] = { return Api.Update.parse_updateChatParticipantAdd($0) } + dict[1851755554] = { return Api.Update.parse_updateChatParticipantDelete($0) } dict[-1906403213] = { return Api.Update.parse_updateDcOptions($0) } dict[-1094555409] = { return Api.Update.parse_updateNotifySettings($0) } dict[-337352679] = { return Api.Update.parse_updateServiceNotification($0) } dict[-298113238] = { return Api.Update.parse_updatePrivacy($0) } - dict[88680979] = { return Api.Update.parse_updateUserPhone($0) } + dict[314130811] = { return Api.Update.parse_updateUserPhone($0) } dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) } dict[791617983] = { return Api.Update.parse_updateReadHistoryOutbox($0) } dict[2139689491] = { return Api.Update.parse_updateWebPage($0) } dict[1757493555] = { return Api.Update.parse_updateReadMessagesContents($0) } - dict[277713951] = { return Api.Update.parse_updateChannelTooLong($0) } - dict[1666927625] = { return Api.Update.parse_updateChannel($0) } + dict[-352032773] = { return Api.Update.parse_updateChannelTooLong($0) } + dict[-1227598250] = { return Api.Update.parse_updateChannel($0) } dict[1656358105] = { return Api.Update.parse_updateNewChannelMessage($0) } - dict[-1842450928] = { return Api.Update.parse_updateReadChannelInbox($0) } - dict[-1020437742] = { return Api.Update.parse_updateDeleteChannelMessages($0) } - dict[-232346616] = { return Api.Update.parse_updateChannelMessageViews($0) } - dict[-674602590] = { return Api.Update.parse_updateChatParticipantAdmin($0) } + dict[856380452] = { return Api.Update.parse_updateReadChannelInbox($0) } + dict[-1015733815] = { return Api.Update.parse_updateDeleteChannelMessages($0) } + dict[-1734268085] = { return Api.Update.parse_updateChannelMessageViews($0) } + dict[-1232070311] = { return Api.Update.parse_updateChatParticipantAdmin($0) } dict[1753886890] = { return Api.Update.parse_updateNewStickerSet($0) } dict[196268545] = { return Api.Update.parse_updateStickerSetsOrder($0) } dict[1135492588] = { return Api.Update.parse_updateStickerSets($0) } dict[-1821035490] = { return Api.Update.parse_updateSavedGifs($0) } - dict[1232025500] = { return Api.Update.parse_updateBotInlineQuery($0) } - dict[317794823] = { return Api.Update.parse_updateBotInlineSend($0) } + dict[1059076315] = { return Api.Update.parse_updateBotInlineQuery($0) } + dict[239663460] = { return Api.Update.parse_updateBotInlineSend($0) } dict[457133559] = { return Api.Update.parse_updateEditChannelMessage($0) } - dict[-1177566067] = { return Api.Update.parse_updateBotCallbackQuery($0) } + dict[-415938591] = { return Api.Update.parse_updateBotCallbackQuery($0) } dict[-469536605] = { return Api.Update.parse_updateEditMessage($0) } - dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) } - dict[-1218471511] = { return Api.Update.parse_updateReadChannelOutbox($0) } + dict[-103646630] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) } + dict[634833351] = { return Api.Update.parse_updateReadChannelOutbox($0) } dict[-299124375] = { return Api.Update.parse_updateDraftMessage($0) } dict[1461528386] = { return Api.Update.parse_updateReadFeaturedStickers($0) } dict[-1706939360] = { return Api.Update.parse_updateRecentStickers($0) } dict[-1574314746] = { return Api.Update.parse_updateConfig($0) } dict[861169551] = { return Api.Update.parse_updatePtsChanged($0) } - dict[791390623] = { return Api.Update.parse_updateChannelWebPage($0) } + dict[1081547008] = { return Api.Update.parse_updateChannelWebPage($0) } dict[1852826908] = { return Api.Update.parse_updateDialogPinned($0) } dict[-99664734] = { return Api.Update.parse_updatePinnedDialogs($0) } dict[-2095595325] = { return Api.Update.parse_updateBotWebhookJSON($0) } dict[-1684914010] = { return Api.Update.parse_updateBotWebhookJSONQuery($0) } - dict[-1246823043] = { return Api.Update.parse_updateBotShippingQuery($0) } - dict[-1934976362] = { return Api.Update.parse_updateBotPrecheckoutQuery($0) } + dict[-523384512] = { return Api.Update.parse_updateBotShippingQuery($0) } + dict[1563376297] = { return Api.Update.parse_updateBotPrecheckoutQuery($0) } dict[-1425052898] = { return Api.Update.parse_updatePhoneCall($0) } dict[1180041828] = { return Api.Update.parse_updateLangPackTooLong($0) } dict[1442983757] = { return Api.Update.parse_updateLangPack($0) } dict[-451831443] = { return Api.Update.parse_updateFavedStickers($0) } - dict[1153291573] = { return Api.Update.parse_updateChannelReadMessagesContents($0) } + dict[-1987495099] = { return Api.Update.parse_updateChannelReadMessagesContents($0) } dict[1887741886] = { return Api.Update.parse_updateContactsReset($0) } - dict[-1304443240] = { return Api.Update.parse_updateChannelAvailableMessages($0) } + dict[1893427255] = { return Api.Update.parse_updateChannelAvailableMessages($0) } dict[-513517117] = { return Api.Update.parse_updateDialogUnreadMark($0) } dict[-1398708869] = { return Api.Update.parse_updateMessagePoll($0) } dict[1421875280] = { return Api.Update.parse_updateChatDefaultBannedRights($0) } @@ -261,40 +262,40 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-2112423005] = { return Api.Update.parse_updateTheme($0) } dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) } dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) } - dict[274961865] = { return Api.Update.parse_updateMessagePollVote($0) } + dict[938909451] = { return Api.Update.parse_updateMessagePollVote($0) } dict[654302845] = { return Api.Update.parse_updateDialogFilter($0) } dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) } dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) } dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) } - dict[-761649164] = { return Api.Update.parse_updateChannelMessageForwards($0) } - dict[-693004986] = { return Api.Update.parse_updateReadChannelDiscussionInbox($0) } - dict[1767677564] = { return Api.Update.parse_updateReadChannelDiscussionOutbox($0) } + dict[1854571743] = { return Api.Update.parse_updateChannelMessageForwards($0) } + dict[482860628] = { return Api.Update.parse_updateReadChannelDiscussionInbox($0) } + dict[1178116716] = { return Api.Update.parse_updateReadChannelDiscussionOutbox($0) } dict[610945826] = { return Api.Update.parse_updatePeerBlocked($0) } - dict[-1937192669] = { return Api.Update.parse_updateChannelUserTyping($0) } + dict[1796675352] = { return Api.Update.parse_updateChannelUserTyping($0) } dict[-309990731] = { return Api.Update.parse_updatePinnedMessages($0) } - dict[1538885128] = { return Api.Update.parse_updatePinnedChannelMessages($0) } - dict[-124097970] = { return Api.Update.parse_updateChat($0) } + dict[-2054649973] = { return Api.Update.parse_updatePinnedChannelMessages($0) } + dict[321954198] = { return Api.Update.parse_updateChat($0) } dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) } - dict[347227392] = { return Api.Update.parse_updateGroupCall($0) } + dict[-1537295973] = { return Api.Update.parse_updateGroupCall($0) } dict[-1147422299] = { return Api.Update.parse_updatePeerHistoryTTL($0) } - dict[-796432838] = { return Api.Update.parse_updateChatParticipant($0) } - dict[-1738720581] = { return Api.Update.parse_updateChannelParticipant($0) } - dict[-997782967] = { return Api.Update.parse_updateBotStopped($0) } + dict[-206342113] = { return Api.Update.parse_updateChatParticipant($0) } + dict[2146218476] = { return Api.Update.parse_updateChannelParticipant($0) } + dict[133777546] = { return Api.Update.parse_updateBotStopped($0) } dict[192428418] = { return Api.Update.parse_updateGroupCallConnection($0) } - dict[1299263278] = { return Api.Update.parse_updateBotCommands($0) } + dict[-813823885] = { return Api.Update.parse_updateBotCommands($0) } dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[-592373577] = { return Api.GroupCallParticipantVideoSourceGroup.parse_groupCallParticipantVideoSourceGroup($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } - dict[-1072953408] = { return Api.ChannelParticipant.parse_channelParticipant($0) } - dict[682146919] = { return Api.ChannelParticipant.parse_channelParticipantSelf($0) } - dict[803602899] = { return Api.ChannelParticipant.parse_channelParticipantCreator($0) } - dict[885242707] = { return Api.ChannelParticipant.parse_channelParticipantAdmin($0) } - dict[1844969806] = { return Api.ChannelParticipant.parse_channelParticipantBanned($0) } + dict[367766557] = { return Api.ChannelParticipant.parse_channelParticipant($0) } + dict[-1557620115] = { return Api.ChannelParticipant.parse_channelParticipantSelf($0) } + dict[1149094475] = { return Api.ChannelParticipant.parse_channelParticipantCreator($0) } + dict[-859915345] = { return Api.ChannelParticipant.parse_channelParticipantAdmin($0) } + dict[1352785878] = { return Api.ChannelParticipant.parse_channelParticipantBanned($0) } dict[453242886] = { return Api.ChannelParticipant.parse_channelParticipantLeft($0) } - dict[886196148] = { return Api.MessageUserVote.parse_messageUserVote($0) } - dict[1017491692] = { return Api.MessageUserVote.parse_messageUserVoteInputOption($0) } - dict[-1973033641] = { return Api.MessageUserVote.parse_messageUserVoteMultiple($0) } + dict[-1567730343] = { return Api.MessageUserVote.parse_messageUserVote($0) } + dict[909603888] = { return Api.MessageUserVote.parse_messageUserVoteInputOption($0) } + dict[244310238] = { return Api.MessageUserVote.parse_messageUserVoteMultiple($0) } dict[182326673] = { return Api.contacts.Blocked.parse_blocked($0) } dict[-513392236] = { return Api.contacts.Blocked.parse_blockedSlice($0) } dict[-55902537] = { return Api.InputDialogPeer.parse_inputDialogPeer($0) } @@ -310,11 +311,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[280464681] = { return Api.KeyboardButton.parse_keyboardButtonUrlAuth($0) } dict[-802258988] = { return Api.KeyboardButton.parse_inputKeyboardButtonUrlAuth($0) } dict[-1144565411] = { return Api.KeyboardButton.parse_keyboardButtonRequestPoll($0) } - dict[383348795] = { return Api.ContactStatus.parse_contactStatus($0) } + dict[-748155807] = { return Api.ContactStatus.parse_contactStatus($0) } dict[1679398724] = { return Api.SecureFile.parse_secureFileEmpty($0) } dict[-534283678] = { return Api.SecureFile.parse_secureFile($0) } dict[-199313886] = { return Api.account.Themes.parse_themesNotModified($0) } - dict[-1707242387] = { return Api.account.Themes.parse_themes($0) } + dict[2137482273] = { return Api.account.Themes.parse_themes($0) } dict[236446268] = { return Api.PhotoSize.parse_photoSizeEmpty($0) } dict[1976012384] = { return Api.PhotoSize.parse_photoSize($0) } dict[35527382] = { return Api.PhotoSize.parse_photoCachedSize($0) } @@ -322,13 +323,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-96535659] = { return Api.PhotoSize.parse_photoSizeProgressive($0) } dict[-668906175] = { return Api.PhotoSize.parse_photoPathSize($0) } dict[-244016606] = { return Api.messages.Stickers.parse_stickersNotModified($0) } - dict[816245886] = { return Api.messages.Stickers.parse_stickers($0) } + dict[-463889475] = { return Api.messages.Stickers.parse_stickers($0) } dict[-1096616924] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) } dict[1008755359] = { return Api.InlineBotSwitchPM.parse_inlineBotSwitchPM($0) } dict[223655517] = { return Api.messages.FoundStickerSets.parse_foundStickerSetsNotModified($0) } - dict[-1963942446] = { return Api.messages.FoundStickerSets.parse_foundStickerSets($0) } + dict[1359533640] = { return Api.messages.FoundStickerSets.parse_foundStickerSets($0) } dict[471437699] = { return Api.account.WallPapers.parse_wallPapersNotModified($0) } - dict[-842824308] = { return Api.account.WallPapers.parse_wallPapers($0) } + dict[1881892265] = { return Api.account.WallPapers.parse_wallPapers($0) } dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) } dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) } dict[-2032041631] = { return Api.Poll.parse_poll($0) } @@ -375,7 +376,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) } dict[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($0) } dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) } - dict[-219353309] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) } + dict[-539872497] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) } dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) } dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) } dict[-1803769784] = { return Api.messages.BotResults.parse_botResults($0) } @@ -401,13 +402,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) } dict[2134579434] = { return Api.InputPeer.parse_inputPeerEmpty($0) } dict[2107670217] = { return Api.InputPeer.parse_inputPeerSelf($0) } - dict[900291769] = { return Api.InputPeer.parse_inputPeerChat($0) } - dict[-571955892] = { return Api.InputPeer.parse_inputPeerUser($0) } - dict[666680316] = { return Api.InputPeer.parse_inputPeerChannel($0) } - dict[-1468331492] = { return Api.InputPeer.parse_inputPeerUserFromMessage($0) } - dict[-1121318848] = { return Api.InputPeer.parse_inputPeerChannelFromMessage($0) } + dict[396093539] = { return Api.InputPeer.parse_inputPeerChat($0) } + dict[2072935910] = { return Api.InputPeer.parse_inputPeerUser($0) } + dict[548253432] = { return Api.InputPeer.parse_inputPeerChannel($0) } + dict[398123750] = { return Api.InputPeer.parse_inputPeerUserFromMessage($0) } + dict[-1667893317] = { return Api.InputPeer.parse_inputPeerChannelFromMessage($0) } dict[568808380] = { return Api.upload.WebFile.parse_webFile($0) } - dict[341499403] = { return Api.Contact.parse_contact($0) } + dict[-116274796] = { return Api.Contact.parse_contact($0) } dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) } dict[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($0) } dict[1648543603] = { return Api.FileHash.parse_fileHash($0) } @@ -428,8 +429,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[195371015] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowContacts($0) } dict[-697604407] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowAll($0) } dict[-1877932953] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowUsers($0) } - dict[-2079962673] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowChatParticipants($0) } - dict[-380694650] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowChatParticipants($0) } + dict[1283572154] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowChatParticipants($0) } + dict[-668769361] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowChatParticipants($0) } dict[-1058912715] = { return Api.messages.DhConfig.parse_dhConfigNotModified($0) } dict[740433629] = { return Api.messages.DhConfig.parse_dhConfig($0) } dict[-421545947] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeTitle($0) } @@ -450,7 +451,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1599903217] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionTogglePreHistoryHidden($0) } dict[771095562] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionDefaultBannedRights($0) } dict[-1895328189] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStopPoll($0) } - dict[84703944] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLinkedChat($0) } + dict[-1569748965] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLinkedChat($0) } dict[241923758] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLocation($0) } dict[1401984889] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleSlowMode($0) } dict[589338437] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStartGroupCall($0) } @@ -464,7 +465,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-384910503] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteEdit($0) } dict[1048537159] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantVolume($0) } dict[1855199800] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeHistoryTTL($0) } - dict[-1271602504] = { return Api.auth.ExportedAuthorization.parse_exportedAuthorization($0) } + dict[-543777747] = { return Api.auth.ExportedAuthorization.parse_exportedAuthorization($0) } dict[2103482845] = { return Api.SecurePlainData.parse_securePlainPhone($0) } dict[569137759] = { return Api.SecurePlainData.parse_securePlainEmail($0) } dict[-1269012015] = { return Api.messages.AffectedHistory.parse_affectedHistory($0) } @@ -476,11 +477,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) } dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) } dict[649453030] = { return Api.messages.MessageEditData.parse_messageEditData($0) } + dict[1705297877] = { return Api.messages.SponsoredMessages.parse_sponsoredMessages($0) } dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) } dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) } dict[1578088377] = { return Api.messages.HistoryImportParsed.parse_historyImportParsed($0) } dict[-618540889] = { return Api.InputSecureValue.parse_inputSecureValue($0) } - dict[-170029155] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) } + dict[-1506535550] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) } dict[1722786150] = { return Api.help.DeepLinkInfo.parse_deepLinkInfoEmpty($0) } dict[1783556146] = { return Api.help.DeepLinkInfo.parse_deepLinkInfo($0) } dict[-313079300] = { return Api.account.WebAuthorizations.parse_webAuthorizations($0) } @@ -498,7 +500,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) } dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) } dict[-1014526429] = { return Api.help.Country.parse_country($0) } - dict[-1660637285] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) } + dict[418631927] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) } dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) } dict[-1038136962] = { return Api.EncryptedFile.parse_encryptedFileEmpty($0) } dict[1248893260] = { return Api.EncryptedFile.parse_encryptedFile($0) } @@ -534,7 +536,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-2049074735] = { return Api.ReplyMarkup.parse_replyKeyboardMarkup($0) } dict[1218642516] = { return Api.ReplyMarkup.parse_replyInlineMarkup($0) } dict[1556570557] = { return Api.EmojiKeywordsDifference.parse_emojiKeywordsDifference($0) } - dict[1940093419] = { return Api.HighScore.parse_highScore($0) } + dict[1493171408] = { return Api.HighScore.parse_highScore($0) } dict[-305282981] = { return Api.TopPeer.parse_topPeer($0) } dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } dict[411017418] = { return Api.SecureValue.parse_secureValue($0) } @@ -546,8 +548,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-386039788] = { return Api.PeerBlocked.parse_peerBlocked($0) } dict[-1182234929] = { return Api.InputUser.parse_inputUserEmpty($0) } dict[-138301121] = { return Api.InputUser.parse_inputUserSelf($0) } - dict[-233744186] = { return Api.InputUser.parse_inputUser($0) } - dict[497305826] = { return Api.InputUser.parse_inputUserFromMessage($0) } + dict[-668391402] = { return Api.InputUser.parse_inputUser($0) } + dict[756118935] = { return Api.InputUser.parse_inputUserFromMessage($0) } dict[-1738178803] = { return Api.Page.parse_page($0) } dict[871426631] = { return Api.SecureCredentialsEncrypted.parse_secureCredentialsEncrypted($0) } dict[-875679776] = { return Api.StatsPercentValue.parse_statsPercentValue($0) } @@ -571,12 +573,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) } dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) } dict[-193506890] = { return Api.phone.GroupParticipants.parse_groupParticipants($0) } - dict[190633460] = { return Api.ChatInviteImporter.parse_chatInviteImporter($0) } + dict[507405952] = { return Api.ChatInviteImporter.parse_chatInviteImporter($0) } dict[-2066640507] = { return Api.messages.AffectedMessages.parse_affectedMessages($0) } dict[-402498398] = { return Api.messages.SavedGifs.parse_savedGifsNotModified($0) } - dict[-2069878259] = { return Api.messages.SavedGifs.parse_savedGifs($0) } + dict[772213157] = { return Api.messages.SavedGifs.parse_savedGifs($0) } dict[-914167110] = { return Api.CdnPublicKey.parse_cdnPublicKey($0) } - dict[-2083123262] = { return Api.MessageReplies.parse_messageReplies($0) } + dict[1093204652] = { return Api.MessageReplies.parse_messageReplies($0) } dict[53231223] = { return Api.InputGame.parse_inputGameID($0) } dict[-1020139510] = { return Api.InputGame.parse_inputGameShortName($0) } dict[1107543535] = { return Api.help.CountryCode.parse_countryCode($0) } @@ -587,6 +589,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1735736008] = { return Api.GroupCallParticipantVideo.parse_groupCallParticipantVideo($0) } dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) } dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) } + dict[-535699004] = { return Api.account.ChatThemes.parse_chatThemesNotModified($0) } + dict[-28524867] = { return Api.account.ChatThemes.parse_chatThemes($0) } dict[-275956116] = { return Api.messages.AffectedFoundMessages.parse_affectedFoundMessages($0) } dict[795652779] = { return Api.BotCommandScope.parse_botCommandScopeDefault($0) } dict[1011811544] = { return Api.BotCommandScope.parse_botCommandScopeUsers($0) } @@ -613,8 +617,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1387279939] = { return Api.MessageInteractionCounters.parse_messageInteractionCounters($0) } dict[-1107852396] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) } dict[-484987010] = { return Api.Updates.parse_updatesTooLong($0) } - dict[826001400] = { return Api.Updates.parse_updateShortMessage($0) } - dict[1299050149] = { return Api.Updates.parse_updateShortChatMessage($0) } + dict[-84936653] = { return Api.Updates.parse_updateShortMessage($0) } + dict[290961496] = { return Api.Updates.parse_updateShortChatMessage($0) } dict[2027216577] = { return Api.Updates.parse_updateShort($0) } dict[1918567619] = { return Api.Updates.parse_updatesCombined($0) } dict[1957577280] = { return Api.Updates.parse_updates($0) } @@ -624,7 +628,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) } dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) } dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) } - dict[1882335561] = { return Api.MessageMedia.parse_messageMediaContact($0) } + dict[-873313984] = { return Api.MessageMedia.parse_messageMediaContact($0) } dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) } dict[-1666158377] = { return Api.MessageMedia.parse_messageMediaDocument($0) } dict[-1557277184] = { return Api.MessageMedia.parse_messageMediaWebPage($0) } @@ -651,8 +655,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[935395612] = { return Api.ChatPhoto.parse_chatPhotoEmpty($0) } dict[476978193] = { return Api.ChatPhoto.parse_chatPhoto($0) } dict[1869903447] = { return Api.PageCaption.parse_pageCaption($0) } - dict[378828315] = { return Api.payments.PaymentForm.parse_paymentForm($0) } - dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) } + dict[-1928649707] = { return Api.payments.PaymentForm.parse_paymentForm($0) } + dict[280319440] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) } dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) } dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[-1886646706] = { return Api.UrlAuthResult.parse_urlAuthResultAccepted($0) } @@ -663,17 +667,17 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[42402760] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmoji($0) } dict[-427863538] = { return Api.InputStickerSet.parse_inputStickerSetDice($0) } dict[-1231326505] = { return Api.messages.ChatAdminsWithInvites.parse_chatAdminsWithInvites($0) } - dict[460632885] = { return Api.BotInfo.parse_botInfo($0) } + dict[-1729618630] = { return Api.BotInfo.parse_botInfo($0) } dict[-2046910401] = { return Api.stickers.SuggestedShortName.parse_suggestedShortName($0) } dict[-1519637954] = { return Api.updates.State.parse_state($0) } - dict[-742634630] = { return Api.User.parse_userEmpty($0) } - dict[1073147056] = { return Api.User.parse_user($0) } + dict[537022650] = { return Api.User.parse_userEmpty($0) } + dict[-1820043071] = { return Api.User.parse_user($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } - dict[-2049520670] = { return Api.Message.parse_message($0) } + dict[-1125940270] = { return Api.Message.parse_message($0) } dict[721967202] = { return Api.Message.parse_messageService($0) } - dict[1398765469] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) } + dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } - dict[-1999405994] = { return Api.messages.RecentStickers.parse_recentStickers($0) } + dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) } dict[-539317279] = { return Api.InputFileLocation.parse_inputFileLocation($0) } dict[-182231723] = { return Api.InputFileLocation.parse_inputEncryptedFileLocation($0) } dict[-1160743548] = { return Api.InputFileLocation.parse_inputDocumentFileLocation($0) } @@ -683,14 +687,14 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-667654413] = { return Api.InputFileLocation.parse_inputPhotoLegacyFileLocation($0) } dict[925204121] = { return Api.InputFileLocation.parse_inputPeerPhotoFileLocation($0) } dict[-1652231205] = { return Api.InputFileLocation.parse_inputStickerSetThumb($0) } - dict[-1146808775] = { return Api.InputFileLocation.parse_inputGroupCallStream($0) } + dict[93890858] = { return Api.InputFileLocation.parse_inputGroupCallStream($0) } dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) } dict[506920429] = { return Api.InputPhoneCall.parse_inputPhoneCall($0) } dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) } dict[-1551583367] = { return Api.ReceivedNotifyMessage.parse_receivedNotifyMessage($0) } - dict[-2023500831] = { return Api.ChatParticipants.parse_chatParticipantsForbidden($0) } - dict[1018991608] = { return Api.ChatParticipants.parse_chatParticipants($0) } + dict[-57668565] = { return Api.ChatParticipants.parse_chatParticipantsForbidden($0) } + dict[1061556205] = { return Api.ChatParticipants.parse_chatParticipants($0) } dict[1949890536] = { return Api.DialogFilter.parse_dialogFilter($0) } dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) } dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) } @@ -705,12 +709,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-433014407] = { return Api.InputWallPaper.parse_inputWallPaper($0) } dict[1913199744] = { return Api.InputWallPaper.parse_inputWallPaperSlug($0) } dict[-1770371538] = { return Api.InputWallPaper.parse_inputWallPaperNoFile($0) } - dict[-1118798639] = { return Api.InputThemeSettings.parse_inputThemeSettings($0) } + dict[-13043438] = { return Api.InputThemeSettings.parse_inputThemeSettings($0) } dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) } dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) } dict[1421174295] = { return Api.WebPageAttribute.parse_webPageAttributeTheme($0) } dict[-958657434] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) } - dict[-2067782896] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) } + dict[-1230257343] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) } + dict[-318022605] = { return Api.ChatTheme.parse_chatTheme($0) } dict[-2048646399] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMissed($0) } dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) } dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) } @@ -722,7 +727,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2010127419] = { return Api.contacts.ImportedContacts.parse_importedContacts($0) } dict[-1678949555] = { return Api.InputWebDocument.parse_inputWebDocument($0) } dict[-326966976] = { return Api.phone.PhoneCall.parse_phoneCall($0) } - dict[531458253] = { return Api.ChannelAdminLogEvent.parse_channelAdminLogEvent($0) } + dict[995769920] = { return Api.ChannelAdminLogEvent.parse_channelAdminLogEvent($0) } dict[-1132882121] = { return Api.Bool.parse_boolFalse($0) } dict[-1720552011] = { return Api.Bool.parse_boolTrue($0) } dict[-892239370] = { return Api.LangPackString.parse_langPackString($0) } @@ -732,6 +737,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) } dict[-1275374751] = { return Api.EmojiLanguage.parse_emojiLanguage($0) } dict[1601666510] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) } + dict[-160304943] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[-1012849566] = { return Api.BaseTheme.parse_baseThemeClassic($0) } dict[-69724536] = { return Api.BaseTheme.parse_baseThemeDay($0) } dict[-1212997976] = { return Api.BaseTheme.parse_baseThemeNight($0) } @@ -772,13 +778,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[894081801] = { return Api.BotInlineMessage.parse_botInlineMessageMediaInvoice($0) } dict[-1673717362] = { return Api.InputPeerNotifySettings.parse_inputPeerNotifySettings($0) } dict[-1634752813] = { return Api.messages.FavedStickers.parse_favedStickersNotModified($0) } - dict[750063767] = { return Api.messages.FavedStickers.parse_favedStickers($0) } - dict[-1316944408] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) } + dict[-209768682] = { return Api.messages.FavedStickers.parse_favedStickers($0) } + dict[1847917725] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) } dict[-1389486888] = { return Api.account.AuthorizationForm.parse_authorizationForm($0) } dict[-1392388579] = { return Api.Authorization.parse_authorization($0) } dict[-1361650766] = { return Api.MaskCoords.parse_maskCoords($0) } dict[-395967805] = { return Api.messages.AllStickers.parse_allStickersNotModified($0) } - dict[-843329861] = { return Api.messages.AllStickers.parse_allStickers($0) } + dict[-302170017] = { return Api.messages.AllStickers.parse_allStickers($0) } dict[-1655957568] = { return Api.PhoneConnection.parse_phoneConnection($0) } dict[1667228533] = { return Api.PhoneConnection.parse_phoneConnectionWebrtc($0) } dict[-206688531] = { return Api.help.UserInfo.parse_userInfoEmpty($0) } @@ -807,23 +813,23 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1352683077] = { return Api.account.PrivacyRules.parse_privacyRules($0) } dict[-123988] = { return Api.PrivacyRule.parse_privacyValueAllowContacts($0) } dict[1698855810] = { return Api.PrivacyRule.parse_privacyValueAllowAll($0) } - dict[-1198497870] = { return Api.PrivacyRule.parse_privacyValueAllowUsers($0) } + dict[1297858060] = { return Api.PrivacyRule.parse_privacyValueAllowUsers($0) } dict[-125240806] = { return Api.PrivacyRule.parse_privacyValueDisallowContacts($0) } dict[-1955338397] = { return Api.PrivacyRule.parse_privacyValueDisallowAll($0) } - dict[-463335103] = { return Api.PrivacyRule.parse_privacyValueDisallowUsers($0) } - dict[1796427406] = { return Api.PrivacyRule.parse_privacyValueAllowChatParticipants($0) } - dict[1103656293] = { return Api.PrivacyRule.parse_privacyValueDisallowChatParticipants($0) } + dict[209668535] = { return Api.PrivacyRule.parse_privacyValueDisallowUsers($0) } + dict[415136107] = { return Api.PrivacyRule.parse_privacyValueAllowChatParticipants($0) } + dict[-1397881200] = { return Api.PrivacyRule.parse_privacyValueDisallowChatParticipants($0) } dict[-1230047312] = { return Api.MessageAction.parse_messageActionEmpty($0) } - dict[-1119368275] = { return Api.MessageAction.parse_messageActionChatCreate($0) } + dict[-1503425638] = { return Api.MessageAction.parse_messageActionChatCreate($0) } dict[-1247687078] = { return Api.MessageAction.parse_messageActionChatEditTitle($0) } dict[2144015272] = { return Api.MessageAction.parse_messageActionChatEditPhoto($0) } dict[-1780220945] = { return Api.MessageAction.parse_messageActionChatDeletePhoto($0) } - dict[365886720] = { return Api.MessageAction.parse_messageActionChatAddUser($0) } - dict[-1539362612] = { return Api.MessageAction.parse_messageActionChatDeleteUser($0) } - dict[51520707] = { return Api.MessageAction.parse_messageActionChatJoinedByLink($0) } + dict[1217033015] = { return Api.MessageAction.parse_messageActionChatAddUser($0) } + dict[-1297179892] = { return Api.MessageAction.parse_messageActionChatDeleteUser($0) } + dict[-123931160] = { return Api.MessageAction.parse_messageActionChatJoinedByLink($0) } dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) } - dict[-519864430] = { return Api.MessageAction.parse_messageActionChatMigrateTo($0) } - dict[-365344535] = { return Api.MessageAction.parse_messageActionChannelMigrateFrom($0) } + dict[1371385889] = { return Api.MessageAction.parse_messageActionChatMigrateTo($0) } + dict[-1336546578] = { return Api.MessageAction.parse_messageActionChannelMigrateFrom($0) } dict[-1799538451] = { return Api.MessageAction.parse_messageActionPinMessage($0) } dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($0) } dict[-1834538890] = { return Api.MessageAction.parse_messageActionGameScore($0) } @@ -838,14 +844,15 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-202219658] = { return Api.MessageAction.parse_messageActionContactSignUp($0) } dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) } dict[2047704898] = { return Api.MessageAction.parse_messageActionGroupCall($0) } - dict[1345295095] = { return Api.MessageAction.parse_messageActionInviteToGroupCall($0) } + dict[1991897370] = { return Api.MessageAction.parse_messageActionInviteToGroupCall($0) } dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) } + dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) } - dict[-987599081] = { return Api.PhoneCall.parse_phoneCallWaiting($0) } - dict[347139340] = { return Api.PhoneCall.parse_phoneCallRequested($0) } - dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) } - dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) } + dict[462375633] = { return Api.PhoneCall.parse_phoneCallWaiting($0) } + dict[-2014659757] = { return Api.PhoneCall.parse_phoneCallRequested($0) } + dict[-1719909046] = { return Api.PhoneCall.parse_phoneCallAccepted($0) } + dict[-2025673089] = { return Api.PhoneCall.parse_phoneCall($0) } dict[1355435489] = { return Api.PhoneCall.parse_phoneCallDiscarded($0) } dict[-483352705] = { return Api.help.TermsOfServiceUpdate.parse_termsOfServiceUpdateEmpty($0) } dict[686618977] = { return Api.help.TermsOfServiceUpdate.parse_termsOfServiceUpdate($0) } @@ -854,13 +861,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1363483106] = { return Api.DialogPeer.parse_dialogPeerFolder($0) } dict[475467473] = { return Api.WebDocument.parse_webDocument($0) } dict[-104284986] = { return Api.WebDocument.parse_webDocumentNoProxy($0) } - dict[42930452] = { return Api.Theme.parse_theme($0) } + dict[-402474788] = { return Api.Theme.parse_theme($0) } dict[-1290580579] = { return Api.contacts.Found.parse_found($0) } dict[-368018716] = { return Api.ChannelAdminLogEventsFilter.parse_channelAdminLogEventsFilter($0) } - dict[-1676371894] = { return Api.ThemeSettings.parse_themeSettings($0) } + dict[-1917524116] = { return Api.ThemeSettings.parse_themeSettings($0) } dict[-1353671392] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) } dict[-1995686519] = { return Api.InputBotInlineMessageID.parse_inputBotInlineMessageID($0) } - dict[-1227287081] = { return Api.InputBotInlineMessageID.parse_inputBotInlineMessageID64($0) } dict[-1282352120] = { return Api.PageRelatedArticle.parse_pageRelatedArticle($0) } dict[313694676] = { return Api.StickerPack.parse_stickerPack($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } @@ -885,7 +891,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[681706865] = { return Api.MessageEntity.parse_messageEntityCode($0) } dict[1938967520] = { return Api.MessageEntity.parse_messageEntityPre($0) } dict[1990644519] = { return Api.MessageEntity.parse_messageEntityTextUrl($0) } - dict[-595914432] = { return Api.MessageEntity.parse_messageEntityMentionName($0) } + dict[892193368] = { return Api.MessageEntity.parse_messageEntityMentionName($0) } dict[546203849] = { return Api.MessageEntity.parse_inputMessageEntityMentionName($0) } dict[-1687559349] = { return Api.MessageEntity.parse_messageEntityPhone($0) } dict[1280209983] = { return Api.MessageEntity.parse_messageEntityCashtag($0) } @@ -905,17 +911,17 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1577484359] = { return Api.PageListOrderedItem.parse_pageListOrderedItemText($0) } dict[-1730311882] = { return Api.PageListOrderedItem.parse_pageListOrderedItemBlocks($0) } dict[-1417756512] = { return Api.EncryptedChat.parse_encryptedChatEmpty($0) } - dict[1722964307] = { return Api.EncryptedChat.parse_encryptedChatWaiting($0) } - dict[1223809356] = { return Api.EncryptedChat.parse_encryptedChatRequested($0) } - dict[1643173063] = { return Api.EncryptedChat.parse_encryptedChat($0) } + dict[1006044124] = { return Api.EncryptedChat.parse_encryptedChatWaiting($0) } + dict[1651608194] = { return Api.EncryptedChat.parse_encryptedChatRequested($0) } + dict[-94974410] = { return Api.EncryptedChat.parse_encryptedChat($0) } dict[505183301] = { return Api.EncryptedChat.parse_encryptedChatDiscarded($0) } dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) } dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) } dict[922273905] = { return Api.Document.parse_documentEmpty($0) } dict[512177195] = { return Api.Document.parse_document($0) } dict[-1707344487] = { return Api.messages.HighScores.parse_highScores($0) } - dict[-1493633966] = { return Api.WebAuthorization.parse_webAuthorization($0) } - dict[-1052885936] = { return Api.ImportedContact.parse_importedContact($0) } + dict[-892779534] = { return Api.WebAuthorization.parse_webAuthorization($0) } + dict[-805141448] = { return Api.ImportedContact.parse_importedContact($0) } dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) } return dict }() @@ -1232,6 +1238,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.MessageEditData: _1.serialize(buffer, boxed) + case let _1 as Api.messages.SponsoredMessages: + _1.serialize(buffer, boxed) case let _1 as Api.LabeledPrice: _1.serialize(buffer, boxed) case let _1 as Api.messages.ChatFull: @@ -1358,6 +1366,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.StatsDateRangeDays: _1.serialize(buffer, boxed) + case let _1 as Api.account.ChatThemes: + _1.serialize(buffer, boxed) case let _1 as Api.messages.AffectedFoundMessages: _1.serialize(buffer, boxed) case let _1 as Api.BotCommandScope: @@ -1470,6 +1480,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.FeaturedStickers: _1.serialize(buffer, boxed) + case let _1 as Api.ChatTheme: + _1.serialize(buffer, boxed) case let _1 as Api.PhoneCallDiscardReason: _1.serialize(buffer, boxed) case let _1 as Api.NearestDc: @@ -1496,6 +1508,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.MessageFwdHeader: _1.serialize(buffer, boxed) + case let _1 as Api.SponsoredMessage: + _1.serialize(buffer, boxed) case let _1 as Api.BaseTheme: _1.serialize(buffer, boxed) case let _1 as Api.help.Support: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index e6d8cbf6a1..288997a593 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -915,6 +915,66 @@ public struct messages { } } + } + public enum SponsoredMessages: TypeConstructorDescription { + case sponsoredMessages(messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sponsoredMessages(let messages, let chats, let users): + if boxed { + buffer.appendInt32(1705297877) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .sponsoredMessages(let messages, let chats, let users): + return ("sponsoredMessages", [("messages", messages), ("chats", chats), ("users", users)]) + } + } + + public static func parse_sponsoredMessages(_ reader: BufferReader) -> SponsoredMessages? { + var _1: [Api.SponsoredMessage]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.SponsoredMessages.sponsoredMessages(messages: _1!, chats: _2!, users: _3!) + } + else { + return nil + } + } + } public enum ChatFull: TypeConstructorDescription { case chatFull(fullChat: Api.ChatFull, chats: [Api.Chat], users: [Api.User]) @@ -1011,13 +1071,13 @@ public struct messages { } public enum DiscussionMessage: TypeConstructorDescription { - case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, chats: [Api.Chat], users: [Api.User]) + case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, unreadCount: Int32, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let chats, let users): + case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chats, let users): if boxed { - buffer.appendInt32(-170029155) + buffer.appendInt32(-1506535550) } serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) @@ -1028,6 +1088,7 @@ public struct messages { if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(readInboxMaxId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(readOutboxMaxId!, buffer: buffer, boxed: false)} + serializeInt32(unreadCount, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { @@ -1044,8 +1105,8 @@ public struct messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let chats, let users): - return ("discussionMessage", [("flags", flags), ("messages", messages), ("maxId", maxId), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("chats", chats), ("users", users)]) + case .discussionMessage(let flags, let messages, let maxId, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chats, let users): + return ("discussionMessage", [("flags", flags), ("messages", messages), ("maxId", maxId), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chats", chats), ("users", users)]) } } @@ -1062,13 +1123,15 @@ public struct messages { if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } var _5: Int32? if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } - var _6: [Api.Chat]? + var _6: Int32? + _6 = reader.readInt32() + var _7: [Api.Chat]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _7: [Api.User]? + var _8: [Api.User]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil @@ -1077,8 +1140,9 @@ public struct messages { let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.DiscussionMessage.discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, chats: _6!, users: _7!) + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.messages.DiscussionMessage.discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, unreadCount: _6!, chats: _7!, users: _8!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index ec005626c5..6c75fd5404 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -160,17 +160,17 @@ public extension Api { } public enum ChatFull: TypeConstructorDescription { - case chatFull(flags: Int32, id: Int64, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttlPeriod: Int32?, groupcallDefaultJoinAs: Api.Peer?) - case channelFull(flags: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?) + case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttlPeriod: Int32?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?) + case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs): + case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon): if boxed { - buffer.appendInt32(1509543498) + buffer.appendInt32(1235264985) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) serializeString(about, buffer: buffer, boxed: false) participants.serialize(buffer, true) if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)} @@ -186,13 +186,14 @@ public extension Api { if Int(flags) & Int(1 << 12) != 0 {call!.serialize(buffer, true)} if Int(flags) & Int(1 << 14) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 15) != 0 {groupcallDefaultJoinAs!.serialize(buffer, true)} + if Int(flags) & Int(1 << 16) != 0 {serializeString(themeEmoticon!, buffer: buffer, boxed: false)} break - case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs): + case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon): if boxed { - buffer.appendInt32(1517757976) + buffer.appendInt32(793980732) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) serializeString(about, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(participantsCount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(adminsCount!, buffer: buffer, boxed: false)} @@ -210,13 +211,13 @@ public extension Api { for item in botInfo { item.serialize(buffer, true) } - if Int(flags) & Int(1 << 4) != 0 {serializeInt64(migratedFromChatId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(migratedFromChatId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {serializeInt32(migratedFromMaxId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 5) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {stickerset!.serialize(buffer, true)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(availableMinId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 14) != 0 {serializeInt64(linkedChatId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 14) != 0 {serializeInt32(linkedChatId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 15) != 0 {location!.serialize(buffer, true)} if Int(flags) & Int(1 << 17) != 0 {serializeInt32(slowmodeSeconds!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 18) != 0 {serializeInt32(slowmodeNextSendDate!, buffer: buffer, boxed: false)} @@ -230,24 +231,25 @@ public extension Api { serializeString(item, buffer: buffer, boxed: false) }} if Int(flags) & Int(1 << 26) != 0 {groupcallDefaultJoinAs!.serialize(buffer, true)} + if Int(flags) & Int(1 << 27) != 0 {serializeString(themeEmoticon!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs): - return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call), ("ttlPeriod", ttlPeriod), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs)]) - case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs): - return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call), ("ttlPeriod", ttlPeriod), ("pendingSuggestions", pendingSuggestions), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs)]) + case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon): + return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call), ("ttlPeriod", ttlPeriod), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon)]) + case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon): + return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call), ("ttlPeriod", ttlPeriod), ("pendingSuggestions", pendingSuggestions), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon)]) } } public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: String? _3 = parseString(reader) var _4: Api.ChatParticipants? @@ -284,6 +286,8 @@ public extension Api { if Int(_1!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() { _13 = Api.parse(reader, signature: signature) as? Api.Peer } } + var _14: String? + if Int(_1!) & Int(1 << 16) != 0 {_14 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -297,8 +301,9 @@ public extension Api { let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 14) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13) + let _c14 = (Int(_1!) & Int(1 << 16) == 0) || _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13, themeEmoticon: _14) } else { return nil @@ -307,8 +312,8 @@ public extension Api { public static func parse_channelFull(_ reader: BufferReader) -> ChatFull? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: String? _3 = parseString(reader) var _4: Int32? @@ -343,8 +348,8 @@ public extension Api { if let _ = reader.readInt32() { _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self) } - var _16: Int64? - if Int(_1!) & Int(1 << 4) != 0 {_16 = reader.readInt64() } + var _16: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_16 = reader.readInt32() } var _17: Int32? if Int(_1!) & Int(1 << 4) != 0 {_17 = reader.readInt32() } var _18: Int32? @@ -357,8 +362,8 @@ public extension Api { if Int(_1!) & Int(1 << 9) != 0 {_20 = reader.readInt32() } var _21: Int32? if Int(_1!) & Int(1 << 11) != 0 {_21 = reader.readInt32() } - var _22: Int64? - if Int(_1!) & Int(1 << 14) != 0 {_22 = reader.readInt64() } + var _22: Int32? + if Int(_1!) & Int(1 << 14) != 0 {_22 = reader.readInt32() } var _23: Api.ChannelLocation? if Int(_1!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() { _23 = Api.parse(reader, signature: signature) as? Api.ChannelLocation @@ -385,6 +390,8 @@ public extension Api { if Int(_1!) & Int(1 << 26) != 0 {if let signature = reader.readInt32() { _31 = Api.parse(reader, signature: signature) as? Api.Peer } } + var _32: String? + if Int(_1!) & Int(1 << 27) != 0 {_32 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -416,8 +423,9 @@ public extension Api { let _c29 = (Int(_1!) & Int(1 << 24) == 0) || _29 != nil let _c30 = (Int(_1!) & Int(1 << 25) == 0) || _30 != nil let _c31 = (Int(_1!) & Int(1 << 26) == 0) || _31 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 { - return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!, call: _28, ttlPeriod: _29, pendingSuggestions: _30, groupcallDefaultJoinAs: _31) + let _c32 = (Int(_1!) & Int(1 << 27) == 0) || _32 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 { + return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!, call: _28, ttlPeriod: _29, pendingSuggestions: _30, groupcallDefaultJoinAs: _31, themeEmoticon: _32) } else { return nil @@ -426,13 +434,13 @@ public extension Api { } public enum PollResults: TypeConstructorDescription { - case pollResults(flags: Int32, results: [Api.PollAnswerVoters]?, totalVoters: Int32?, recentVoters: [Int64]?, solution: String?, solutionEntities: [Api.MessageEntity]?) + case pollResults(flags: Int32, results: [Api.PollAnswerVoters]?, totalVoters: Int32?, recentVoters: [Int32]?, solution: String?, solutionEntities: [Api.MessageEntity]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .pollResults(let flags, let results, let totalVoters, let recentVoters, let solution, let solutionEntities): if boxed { - buffer.appendInt32(-591909213) + buffer.appendInt32(-1159937629) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) @@ -444,7 +452,7 @@ public extension Api { if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) buffer.appendInt32(Int32(recentVoters!.count)) for item in recentVoters! { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) }} if Int(flags) & Int(1 << 4) != 0 {serializeString(solution!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) @@ -472,9 +480,9 @@ public extension Api { } } var _3: Int32? if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } - var _4: [Int64]? + var _4: [Int32]? if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } } var _5: String? if Int(_1!) & Int(1 << 4) != 0 {_5 = parseString(reader) } @@ -498,32 +506,32 @@ public extension Api { } public enum ChatParticipant: TypeConstructorDescription { - case chatParticipant(userId: Int64, inviterId: Int64, date: Int32) - case chatParticipantCreator(userId: Int64) - case chatParticipantAdmin(userId: Int64, inviterId: Int64, date: Int32) + case chatParticipant(userId: Int32, inviterId: Int32, date: Int32) + case chatParticipantCreator(userId: Int32) + case chatParticipantAdmin(userId: Int32, inviterId: Int32, date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .chatParticipant(let userId, let inviterId, let date): if boxed { - buffer.appendInt32(-1070776313) + buffer.appendInt32(-925415106) } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt64(inviterId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(inviterId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break case .chatParticipantCreator(let userId): if boxed { - buffer.appendInt32(-462696732) + buffer.appendInt32(-636267638) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) break case .chatParticipantAdmin(let userId, let inviterId, let date): if boxed { - buffer.appendInt32(-1600962725) + buffer.appendInt32(-489233354) } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt64(inviterId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(inviterId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break } @@ -541,10 +549,10 @@ public extension Api { } public static func parse_chatParticipant(_ reader: BufferReader) -> ChatParticipant? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() let _c1 = _1 != nil @@ -558,8 +566,8 @@ public extension Api { } } public static func parse_chatParticipantCreator(_ reader: BufferReader) -> ChatParticipant? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.ChatParticipant.chatParticipantCreator(userId: _1!) @@ -569,10 +577,10 @@ public extension Api { } } public static func parse_chatParticipantAdmin(_ reader: BufferReader) -> ChatParticipant? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() let _c1 = _1 != nil @@ -1744,26 +1752,26 @@ public extension Api { } public enum Chat: TypeConstructorDescription { - case chatEmpty(id: Int64) - case chat(flags: Int32, id: Int64, title: String, photo: Api.ChatPhoto, participantsCount: Int32, date: Int32, version: Int32, migratedTo: Api.InputChannel?, adminRights: Api.ChatAdminRights?, defaultBannedRights: Api.ChatBannedRights?) - case chatForbidden(id: Int64, title: String) - case channel(flags: Int32, id: Int64, accessHash: Int64?, title: String, username: String?, photo: Api.ChatPhoto, date: Int32, restrictionReason: [Api.RestrictionReason]?, adminRights: Api.ChatAdminRights?, bannedRights: Api.ChatBannedRights?, defaultBannedRights: Api.ChatBannedRights?, participantsCount: Int32?) - case channelForbidden(flags: Int32, id: Int64, accessHash: Int64, title: String, untilDate: Int32?) + case chatEmpty(id: Int32) + case chat(flags: Int32, id: Int32, title: String, photo: Api.ChatPhoto, participantsCount: Int32, date: Int32, version: Int32, migratedTo: Api.InputChannel?, adminRights: Api.ChatAdminRights?, defaultBannedRights: Api.ChatBannedRights?) + case chatForbidden(id: Int32, title: String) + case channel(flags: Int32, id: Int32, accessHash: Int64?, title: String, username: String?, photo: Api.ChatPhoto, date: Int32, version: Int32, restrictionReason: [Api.RestrictionReason]?, adminRights: Api.ChatAdminRights?, bannedRights: Api.ChatBannedRights?, defaultBannedRights: Api.ChatBannedRights?, participantsCount: Int32?) + case channelForbidden(flags: Int32, id: Int32, accessHash: Int64, title: String, untilDate: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .chatEmpty(let id): if boxed { - buffer.appendInt32(693512293) + buffer.appendInt32(-1683826688) } - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) break case .chat(let flags, let id, let title, let photo, let participantsCount, let date, let version, let migratedTo, let adminRights, let defaultBannedRights): if boxed { - buffer.appendInt32(1103884886) + buffer.appendInt32(1004149726) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) photo.serialize(buffer, true) serializeInt32(participantsCount, buffer: buffer, boxed: false) @@ -1775,22 +1783,23 @@ public extension Api { break case .chatForbidden(let id, let title): if boxed { - buffer.appendInt32(1704108455) + buffer.appendInt32(120753115) } - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) break - case .channel(let flags, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount): + case .channel(let flags, let id, let accessHash, let title, let username, let photo, let date, let version, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount): if boxed { - buffer.appendInt32(-2107528095) + buffer.appendInt32(-753232354) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 13) != 0 {serializeInt64(accessHash!, buffer: buffer, boxed: false)} serializeString(title, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 6) != 0 {serializeString(username!, buffer: buffer, boxed: false)} photo.serialize(buffer, true) serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(version, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 9) != 0 {buffer.appendInt32(481674261) buffer.appendInt32(Int32(restrictionReason!.count)) for item in restrictionReason! { @@ -1803,10 +1812,10 @@ public extension Api { break case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate): if boxed { - buffer.appendInt32(399807445) + buffer.appendInt32(681420594) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 16) != 0 {serializeInt32(untilDate!, buffer: buffer, boxed: false)} @@ -1822,16 +1831,16 @@ public extension Api { return ("chat", [("flags", flags), ("id", id), ("title", title), ("photo", photo), ("participantsCount", participantsCount), ("date", date), ("version", version), ("migratedTo", migratedTo), ("adminRights", adminRights), ("defaultBannedRights", defaultBannedRights)]) case .chatForbidden(let id, let title): return ("chatForbidden", [("id", id), ("title", title)]) - case .channel(let flags, let id, let accessHash, let title, let username, let photo, let date, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount): - return ("channel", [("flags", flags), ("id", id), ("accessHash", accessHash), ("title", title), ("username", username), ("photo", photo), ("date", date), ("restrictionReason", restrictionReason), ("adminRights", adminRights), ("bannedRights", bannedRights), ("defaultBannedRights", defaultBannedRights), ("participantsCount", participantsCount)]) + case .channel(let flags, let id, let accessHash, let title, let username, let photo, let date, let version, let restrictionReason, let adminRights, let bannedRights, let defaultBannedRights, let participantsCount): + return ("channel", [("flags", flags), ("id", id), ("accessHash", accessHash), ("title", title), ("username", username), ("photo", photo), ("date", date), ("version", version), ("restrictionReason", restrictionReason), ("adminRights", adminRights), ("bannedRights", bannedRights), ("defaultBannedRights", defaultBannedRights), ("participantsCount", participantsCount)]) case .channelForbidden(let flags, let id, let accessHash, let title, let untilDate): return ("channelForbidden", [("flags", flags), ("id", id), ("accessHash", accessHash), ("title", title), ("untilDate", untilDate)]) } } public static func parse_chatEmpty(_ reader: BufferReader) -> Chat? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.Chat.chatEmpty(id: _1!) @@ -1843,8 +1852,8 @@ public extension Api { public static func parse_chat(_ reader: BufferReader) -> Chat? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: String? _3 = parseString(reader) var _4: Api.ChatPhoto? @@ -1887,8 +1896,8 @@ public extension Api { } } public static func parse_chatForbidden(_ reader: BufferReader) -> Chat? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) let _c1 = _1 != nil @@ -1903,8 +1912,8 @@ public extension Api { public static func parse_channel(_ reader: BufferReader) -> Chat? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int64? if Int(_1!) & Int(1 << 13) != 0 {_3 = reader.readInt64() } var _4: String? @@ -1917,24 +1926,26 @@ public extension Api { } var _7: Int32? _7 = reader.readInt32() - var _8: [Api.RestrictionReason]? + var _8: Int32? + _8 = reader.readInt32() + var _9: [Api.RestrictionReason]? if Int(_1!) & Int(1 << 9) != 0 {if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } - var _9: Api.ChatAdminRights? + var _10: Api.ChatAdminRights? if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } } - var _10: Api.ChatBannedRights? - if Int(_1!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights + _10 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights } } var _11: Api.ChatBannedRights? - if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() { + if Int(_1!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() { _11 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights } } - var _12: Int32? - if Int(_1!) & Int(1 << 17) != 0 {_12 = reader.readInt32() } + var _12: Api.ChatBannedRights? + if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights + } } + var _13: Int32? + if Int(_1!) & Int(1 << 17) != 0 {_13 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 13) == 0) || _3 != nil @@ -1942,13 +1953,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 6) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 9) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 14) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 15) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 18) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 17) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.Chat.channel(flags: _1!, id: _2!, accessHash: _3, title: _4!, username: _5, photo: _6!, date: _7!, restrictionReason: _8, adminRights: _9, bannedRights: _10, defaultBannedRights: _11, participantsCount: _12) + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 9) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 15) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 17) == 0) || _13 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { + return Api.Chat.channel(flags: _1!, id: _2!, accessHash: _3, title: _4!, username: _5, photo: _6!, date: _7!, version: _8!, restrictionReason: _9, adminRights: _10, bannedRights: _11, defaultBannedRights: _12, participantsCount: _13) } else { return nil @@ -1957,8 +1969,8 @@ public extension Api { public static func parse_channelForbidden(_ reader: BufferReader) -> Chat? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int64? _3 = reader.readInt64() var _4: String? @@ -2319,8 +2331,8 @@ public extension Api { } public enum RecentMeUrl: TypeConstructorDescription { case recentMeUrlUnknown(url: String) - case recentMeUrlUser(url: String, userId: Int64) - case recentMeUrlChat(url: String, chatId: Int64) + case recentMeUrlUser(url: String, userId: Int32) + case recentMeUrlChat(url: String, chatId: Int32) case recentMeUrlChatInvite(url: String, chatInvite: Api.ChatInvite) case recentMeUrlStickerSet(url: String, set: Api.StickerSetCovered) @@ -2334,17 +2346,17 @@ public extension Api { break case .recentMeUrlUser(let url, let userId): if boxed { - buffer.appendInt32(-1188296222) + buffer.appendInt32(-1917045962) } serializeString(url, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) break case .recentMeUrlChat(let url, let chatId): if boxed { - buffer.appendInt32(-1294306862) + buffer.appendInt32(-1608834311) } serializeString(url, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) break case .recentMeUrlChatInvite(let url, let chatInvite): if boxed { @@ -2392,8 +2404,8 @@ public extension Api { public static func parse_recentMeUrlUser(_ reader: BufferReader) -> RecentMeUrl? { var _1: String? _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { @@ -2406,8 +2418,8 @@ public extension Api { public static func parse_recentMeUrlChat(_ reader: BufferReader) -> RecentMeUrl? { var _1: String? _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { @@ -2878,13 +2890,13 @@ public extension Api { } public enum UserFull: TypeConstructorDescription { - case userFull(flags: Int32, user: Api.User, about: String?, settings: Api.PeerSettings, profilePhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?) + case userFull(flags: Int32, user: Api.User, about: String?, settings: Api.PeerSettings, profilePhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let flags, let user, let about, let settings, let profilePhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod): + case .userFull(let flags, let user, let about, let settings, let profilePhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon): if boxed { - buffer.appendInt32(328899191) + buffer.appendInt32(-694681851) } serializeInt32(flags, buffer: buffer, boxed: false) user.serialize(buffer, true) @@ -2897,14 +2909,15 @@ public extension Api { serializeInt32(commonChatsCount, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 14) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 15) != 0 {serializeString(themeEmoticon!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userFull(let flags, let user, let about, let settings, let profilePhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod): - return ("userFull", [("flags", flags), ("user", user), ("about", about), ("settings", settings), ("profilePhoto", profilePhoto), ("notifySettings", notifySettings), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("commonChatsCount", commonChatsCount), ("folderId", folderId), ("ttlPeriod", ttlPeriod)]) + case .userFull(let flags, let user, let about, let settings, let profilePhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon): + return ("userFull", [("flags", flags), ("user", user), ("about", about), ("settings", settings), ("profilePhoto", profilePhoto), ("notifySettings", notifySettings), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("commonChatsCount", commonChatsCount), ("folderId", folderId), ("ttlPeriod", ttlPeriod), ("themeEmoticon", themeEmoticon)]) } } @@ -2941,6 +2954,8 @@ public extension Api { if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() } var _11: Int32? if Int(_1!) & Int(1 << 14) != 0 {_11 = reader.readInt32() } + var _12: String? + if Int(_1!) & Int(1 << 15) != 0 {_12 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil @@ -2952,8 +2967,9 @@ public extension Api { let _c9 = _9 != nil let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 14) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.UserFull.userFull(flags: _1!, user: _2!, about: _3, settings: _4!, profilePhoto: _5, notifySettings: _6!, botInfo: _7, pinnedMsgId: _8, commonChatsCount: _9!, folderId: _10, ttlPeriod: _11) + let _c12 = (Int(_1!) & Int(1 << 15) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.UserFull.userFull(flags: _1!, user: _2!, about: _3, settings: _4!, profilePhoto: _5, notifySettings: _6!, botInfo: _7, pinnedMsgId: _8, commonChatsCount: _9!, folderId: _10, ttlPeriod: _11, themeEmoticon: _12) } else { return nil @@ -2963,8 +2979,8 @@ public extension Api { } public enum InputChannel: TypeConstructorDescription { case inputChannelEmpty - case inputChannel(channelId: Int64, accessHash: Int64) - case inputChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int64) + case inputChannel(channelId: Int32, accessHash: Int64) + case inputChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -2976,18 +2992,18 @@ public extension Api { break case .inputChannel(let channelId, let accessHash): if boxed { - buffer.appendInt32(-212145112) + buffer.appendInt32(-1343524562) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) break case .inputChannelFromMessage(let peer, let msgId, let channelId): if boxed { - buffer.appendInt32(1536380829) + buffer.appendInt32(707290417) } peer.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) break } } @@ -3007,8 +3023,8 @@ public extension Api { return Api.InputChannel.inputChannelEmpty } public static func parse_inputChannel(_ reader: BufferReader) -> InputChannel? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() let _c1 = _1 != nil @@ -3027,8 +3043,8 @@ public extension Api { } var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -3398,15 +3414,15 @@ public extension Api { } public enum StatsGroupTopAdmin: TypeConstructorDescription { - case statsGroupTopAdmin(userId: Int64, deleted: Int32, kicked: Int32, banned: Int32) + case statsGroupTopAdmin(userId: Int32, deleted: Int32, kicked: Int32, banned: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .statsGroupTopAdmin(let userId, let deleted, let kicked, let banned): if boxed { - buffer.appendInt32(-682079097) + buffer.appendInt32(1611985938) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(deleted, buffer: buffer, boxed: false) serializeInt32(kicked, buffer: buffer, boxed: false) serializeInt32(banned, buffer: buffer, boxed: false) @@ -3422,8 +3438,8 @@ public extension Api { } public static func parse_statsGroupTopAdmin(_ reader: BufferReader) -> StatsGroupTopAdmin? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() var _3: Int32? @@ -3842,29 +3858,29 @@ public extension Api { } public enum Peer: TypeConstructorDescription { - case peerUser(userId: Int64) - case peerChat(chatId: Int64) - case peerChannel(channelId: Int64) + case peerUser(userId: Int32) + case peerChat(chatId: Int32) + case peerChannel(channelId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .peerUser(let userId): if boxed { - buffer.appendInt32(1498486562) + buffer.appendInt32(-1649296275) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) break case .peerChat(let chatId): if boxed { - buffer.appendInt32(918946202) + buffer.appendInt32(-1160714821) } - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) break case .peerChannel(let channelId): if boxed { - buffer.appendInt32(-1566230754) + buffer.appendInt32(-1109531342) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) break } } @@ -3881,8 +3897,8 @@ public extension Api { } public static func parse_peerUser(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.Peer.peerUser(userId: _1!) @@ -3892,8 +3908,8 @@ public extension Api { } } public static func parse_peerChat(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.Peer.peerChat(chatId: _1!) @@ -3903,8 +3919,8 @@ public extension Api { } } public static func parse_peerChannel(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.Peer.peerChannel(channelId: _1!) @@ -4265,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 { @@ -4357,6 +4374,12 @@ public extension Api { buffer.appendInt32(-606432698) } serializeInt32(progress, buffer: buffer, boxed: false) + break + case .sendMessageChooseStickerAction: + if boxed { + buffer.appendInt32(-1336228175) + } + break } } @@ -4393,6 +4416,8 @@ public extension Api { return ("speakingInGroupCallAction", []) case .sendMessageHistoryImportAction(let progress): return ("sendMessageHistoryImportAction", [("progress", progress)]) + case .sendMessageChooseStickerAction: + return ("sendMessageChooseStickerAction", []) } } @@ -4489,6 +4514,9 @@ public extension Api { return nil } } + public static func parse_sendMessageChooseStickerAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageChooseStickerAction + } } public enum PrivacyKey: TypeConstructorDescription { @@ -4605,64 +4633,64 @@ public extension Api { case updateNewMessage(message: Api.Message, pts: Int32, ptsCount: Int32) case updateMessageID(id: Int32, randomId: Int64) case updateDeleteMessages(messages: [Int32], pts: Int32, ptsCount: Int32) - case updateUserTyping(userId: Int64, action: Api.SendMessageAction) - case updateChatUserTyping(chatId: Int64, fromId: Api.Peer, action: Api.SendMessageAction) + case updateUserTyping(userId: Int32, action: Api.SendMessageAction) + case updateChatUserTyping(chatId: Int32, fromId: Api.Peer, action: Api.SendMessageAction) case updateChatParticipants(participants: Api.ChatParticipants) - case updateUserStatus(userId: Int64, status: Api.UserStatus) - case updateUserName(userId: Int64, firstName: String, lastName: String, username: String) - case updateUserPhoto(userId: Int64, date: Int32, photo: Api.UserProfilePhoto, previous: Api.Bool) + case updateUserStatus(userId: Int32, status: Api.UserStatus) + case updateUserName(userId: Int32, firstName: String, lastName: String, username: String) + case updateUserPhoto(userId: Int32, date: Int32, photo: Api.UserProfilePhoto, previous: Api.Bool) case updateNewEncryptedMessage(message: Api.EncryptedMessage, qts: Int32) case updateEncryptedChatTyping(chatId: Int32) case updateEncryption(chat: Api.EncryptedChat, date: Int32) case updateEncryptedMessagesRead(chatId: Int32, maxDate: Int32, date: Int32) - case updateChatParticipantAdd(chatId: Int64, userId: Int64, inviterId: Int64, date: Int32, version: Int32) - case updateChatParticipantDelete(chatId: Int64, userId: Int64, version: Int32) + case updateChatParticipantAdd(chatId: Int32, userId: Int32, inviterId: Int32, date: Int32, version: Int32) + case updateChatParticipantDelete(chatId: Int32, userId: Int32, version: Int32) case updateDcOptions(dcOptions: [Api.DcOption]) case updateNotifySettings(peer: Api.NotifyPeer, notifySettings: Api.PeerNotifySettings) case updateServiceNotification(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity]) case updatePrivacy(key: Api.PrivacyKey, rules: [Api.PrivacyRule]) - case updateUserPhone(userId: Int64, phone: String) + case updateUserPhone(userId: Int32, phone: String) case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32) case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32) case updateWebPage(webpage: Api.WebPage, pts: Int32, ptsCount: Int32) case updateReadMessagesContents(messages: [Int32], pts: Int32, ptsCount: Int32) - case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?) - case updateChannel(channelId: Int64) + case updateChannelTooLong(flags: Int32, channelId: Int32, pts: Int32?) + case updateChannel(channelId: Int32) case updateNewChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) - case updateReadChannelInbox(flags: Int32, folderId: Int32?, channelId: Int64, maxId: Int32, stillUnreadCount: Int32, pts: Int32) - case updateDeleteChannelMessages(channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) - case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32) - case updateChatParticipantAdmin(chatId: Int64, userId: Int64, isAdmin: Api.Bool, version: Int32) + case updateReadChannelInbox(flags: Int32, folderId: Int32?, channelId: Int32, maxId: Int32, stillUnreadCount: Int32, pts: Int32) + case updateDeleteChannelMessages(channelId: Int32, messages: [Int32], pts: Int32, ptsCount: Int32) + case updateChannelMessageViews(channelId: Int32, id: Int32, views: Int32) + case updateChatParticipantAdmin(chatId: Int32, userId: Int32, isAdmin: Api.Bool, version: Int32) case updateNewStickerSet(stickerset: Api.messages.StickerSet) case updateStickerSetsOrder(flags: Int32, order: [Int64]) case updateStickerSets case updateSavedGifs - case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int64, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String) - case updateBotInlineSend(flags: Int32, userId: Int64, query: String, geo: Api.GeoPoint?, id: String, msgId: Api.InputBotInlineMessageID?) + case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int32, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String) + case updateBotInlineSend(flags: Int32, userId: Int32, query: String, geo: Api.GeoPoint?, id: String, msgId: Api.InputBotInlineMessageID?) case updateEditChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) - case updateBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?) + case updateBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int32, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?) case updateEditMessage(message: Api.Message, pts: Int32, ptsCount: Int32) - case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) - case updateReadChannelOutbox(channelId: Int64, maxId: Int32) + case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int32, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) + case updateReadChannelOutbox(channelId: Int32, maxId: Int32) case updateDraftMessage(peer: Api.Peer, draft: Api.DraftMessage) case updateReadFeaturedStickers case updateRecentStickers case updateConfig case updatePtsChanged - case updateChannelWebPage(channelId: Int64, webpage: Api.WebPage, pts: Int32, ptsCount: Int32) + case updateChannelWebPage(channelId: Int32, webpage: Api.WebPage, pts: Int32, ptsCount: Int32) case updateDialogPinned(flags: Int32, folderId: Int32?, peer: Api.DialogPeer) case updatePinnedDialogs(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?) case updateBotWebhookJSON(data: Api.DataJSON) case updateBotWebhookJSONQuery(queryId: Int64, data: Api.DataJSON, timeout: Int32) - case updateBotShippingQuery(queryId: Int64, userId: Int64, payload: Buffer, shippingAddress: Api.PostAddress) - case updateBotPrecheckoutQuery(flags: Int32, queryId: Int64, userId: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, currency: String, totalAmount: Int64) + case updateBotShippingQuery(queryId: Int64, userId: Int32, payload: Buffer, shippingAddress: Api.PostAddress) + case updateBotPrecheckoutQuery(flags: Int32, queryId: Int64, userId: Int32, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, currency: String, totalAmount: Int64) case updatePhoneCall(phoneCall: Api.PhoneCall) case updateLangPackTooLong(langCode: String) case updateLangPack(difference: Api.LangPackDifference) case updateFavedStickers - case updateChannelReadMessagesContents(channelId: Int64, messages: [Int32]) + case updateChannelReadMessagesContents(channelId: Int32, messages: [Int32]) case updateContactsReset - case updateChannelAvailableMessages(channelId: Int64, availableMinId: Int32) + case updateChannelAvailableMessages(channelId: Int32, availableMinId: Int32) case updateDialogUnreadMark(flags: Int32, peer: Api.DialogPeer) case updateMessagePoll(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults) case updateChatDefaultBannedRights(peer: Api.Peer, defaultBannedRights: Api.ChatBannedRights, version: Int32) @@ -4674,27 +4702,27 @@ public extension Api { case updateTheme(theme: Api.Theme) case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32) case updateLoginToken - case updateMessagePollVote(pollId: Int64, userId: Int64, options: [Buffer], qts: Int32) + case updateMessagePollVote(pollId: Int64, userId: Int32, options: [Buffer], qts: Int32) case updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) case updateDialogFilterOrder(order: [Int32]) case updateDialogFilters case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) - case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32) - case updateReadChannelDiscussionInbox(flags: Int32, channelId: Int64, topMsgId: Int32, readMaxId: Int32, broadcastId: Int64?, broadcastPost: Int32?) - case updateReadChannelDiscussionOutbox(channelId: Int64, topMsgId: Int32, readMaxId: Int32) + case updateChannelMessageForwards(channelId: Int32, id: Int32, forwards: Int32) + case updateReadChannelDiscussionInbox(flags: Int32, channelId: Int32, topMsgId: Int32, readMaxId: Int32, broadcastId: Int32?, broadcastPost: Int32?) + case updateReadChannelDiscussionOutbox(channelId: Int32, topMsgId: Int32, readMaxId: Int32) case updatePeerBlocked(peerId: Api.Peer, blocked: Api.Bool) - case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) + case updateChannelUserTyping(flags: Int32, channelId: Int32, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) - case updatePinnedChannelMessages(flags: Int32, channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) - case updateChat(chatId: Int64) + case updatePinnedChannelMessages(flags: Int32, channelId: Int32, messages: [Int32], pts: Int32, ptsCount: Int32) + case updateChat(chatId: Int32) case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) - case updateGroupCall(chatId: Int64, call: Api.GroupCall) + case updateGroupCall(chatId: Int32, call: Api.GroupCall) case updatePeerHistoryTTL(flags: Int32, peer: Api.Peer, ttlPeriod: Int32?) - case updateChatParticipant(flags: Int32, chatId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChatParticipant?, newParticipant: Api.ChatParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) - case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) - case updateBotStopped(userId: Int64, date: Int32, stopped: Api.Bool, qts: Int32) + case updateChatParticipant(flags: Int32, chatId: Int32, date: Int32, actorId: Int32, userId: Int32, prevParticipant: Api.ChatParticipant?, newParticipant: Api.ChatParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) + case updateChannelParticipant(flags: Int32, channelId: Int32, date: Int32, actorId: Int32, userId: Int32, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) + case updateBotStopped(userId: Int32, date: Int32, stopped: Api.Bool, qts: Int32) case updateGroupCallConnection(flags: Int32, params: Api.DataJSON) - case updateBotCommands(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand]) + case updateBotCommands(peer: Api.Peer, botId: Int32, commands: [Api.BotCommand]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -4727,16 +4755,16 @@ public extension Api { break case .updateUserTyping(let userId, let action): if boxed { - buffer.appendInt32(-1071741569) + buffer.appendInt32(1548249383) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) action.serialize(buffer, true) break case .updateChatUserTyping(let chatId, let fromId, let action): if boxed { - buffer.appendInt32(-2092401936) + buffer.appendInt32(-2033525908) } - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) fromId.serialize(buffer, true) action.serialize(buffer, true) break @@ -4748,25 +4776,25 @@ public extension Api { break case .updateUserStatus(let userId, let status): if boxed { - buffer.appendInt32(-440534818) + buffer.appendInt32(469489699) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) status.serialize(buffer, true) break case .updateUserName(let userId, let firstName, let lastName, let username): if boxed { - buffer.appendInt32(-1007549728) + buffer.appendInt32(-1489818765) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeString(firstName, buffer: buffer, boxed: false) serializeString(lastName, buffer: buffer, boxed: false) serializeString(username, buffer: buffer, boxed: false) break case .updateUserPhoto(let userId, let date, let photo, let previous): if boxed { - buffer.appendInt32(-232290676) + buffer.appendInt32(-1791935732) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) photo.serialize(buffer, true) previous.serialize(buffer, true) @@ -4801,20 +4829,20 @@ public extension Api { break case .updateChatParticipantAdd(let chatId, let userId, let inviterId, let date, let version): if boxed { - buffer.appendInt32(1037718609) + buffer.appendInt32(-364179876) } - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt64(inviterId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(inviterId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) serializeInt32(version, buffer: buffer, boxed: false) break case .updateChatParticipantDelete(let chatId, let userId, let version): if boxed { - buffer.appendInt32(-483443337) + buffer.appendInt32(1851755554) } - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(version, buffer: buffer, boxed: false) break case .updateDcOptions(let dcOptions): @@ -4862,9 +4890,9 @@ public extension Api { break case .updateUserPhone(let userId, let phone): if boxed { - buffer.appendInt32(88680979) + buffer.appendInt32(314130811) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeString(phone, buffer: buffer, boxed: false) break case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount): @@ -4910,17 +4938,17 @@ public extension Api { break case .updateChannelTooLong(let flags, let channelId, let pts): if boxed { - buffer.appendInt32(277713951) + buffer.appendInt32(-352032773) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(pts!, buffer: buffer, boxed: false)} break case .updateChannel(let channelId): if boxed { - buffer.appendInt32(1666927625) + buffer.appendInt32(-1227598250) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) break case .updateNewChannelMessage(let message, let pts, let ptsCount): if boxed { @@ -4932,20 +4960,20 @@ public extension Api { break case .updateReadChannelInbox(let flags, let folderId, let channelId, let maxId, let stillUnreadCount, let pts): if boxed { - buffer.appendInt32(-1842450928) + buffer.appendInt32(856380452) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt32(maxId, buffer: buffer, boxed: false) serializeInt32(stillUnreadCount, buffer: buffer, boxed: false) serializeInt32(pts, buffer: buffer, boxed: false) break case .updateDeleteChannelMessages(let channelId, let messages, let pts, let ptsCount): if boxed { - buffer.appendInt32(-1020437742) + buffer.appendInt32(-1015733815) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(messages.count)) for item in messages { @@ -4956,18 +4984,18 @@ public extension Api { break case .updateChannelMessageViews(let channelId, let id, let views): if boxed { - buffer.appendInt32(-232346616) + buffer.appendInt32(-1734268085) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(views, buffer: buffer, boxed: false) break case .updateChatParticipantAdmin(let chatId, let userId, let isAdmin, let version): if boxed { - buffer.appendInt32(-674602590) + buffer.appendInt32(-1232070311) } - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) isAdmin.serialize(buffer, true) serializeInt32(version, buffer: buffer, boxed: false) break @@ -5002,11 +5030,11 @@ public extension Api { break case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset): if boxed { - buffer.appendInt32(1232025500) + buffer.appendInt32(1059076315) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(queryId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeString(query, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {geo!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {peerType!.serialize(buffer, true)} @@ -5014,10 +5042,10 @@ public extension Api { break case .updateBotInlineSend(let flags, let userId, let query, let geo, let id, let msgId): if boxed { - buffer.appendInt32(317794823) + buffer.appendInt32(239663460) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeString(query, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {geo!.serialize(buffer, true)} serializeString(id, buffer: buffer, boxed: false) @@ -5033,11 +5061,11 @@ public extension Api { break case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName): if boxed { - buffer.appendInt32(-1177566067) + buffer.appendInt32(-415938591) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(queryId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) peer.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) serializeInt64(chatInstance, buffer: buffer, boxed: false) @@ -5054,11 +5082,11 @@ public extension Api { break case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName): if boxed { - buffer.appendInt32(1763610706) + buffer.appendInt32(-103646630) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(queryId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) msgId.serialize(buffer, true) serializeInt64(chatInstance, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeBytes(data!, buffer: buffer, boxed: false)} @@ -5066,9 +5094,9 @@ public extension Api { break case .updateReadChannelOutbox(let channelId, let maxId): if boxed { - buffer.appendInt32(-1218471511) + buffer.appendInt32(634833351) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt32(maxId, buffer: buffer, boxed: false) break case .updateDraftMessage(let peer, let draft): @@ -5104,9 +5132,9 @@ public extension Api { break case .updateChannelWebPage(let channelId, let webpage, let pts, let ptsCount): if boxed { - buffer.appendInt32(791390623) + buffer.appendInt32(1081547008) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) webpage.serialize(buffer, true) serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(ptsCount, buffer: buffer, boxed: false) @@ -5147,20 +5175,20 @@ public extension Api { break case .updateBotShippingQuery(let queryId, let userId, let payload, let shippingAddress): if boxed { - buffer.appendInt32(-1246823043) + buffer.appendInt32(-523384512) } serializeInt64(queryId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeBytes(payload, buffer: buffer, boxed: false) shippingAddress.serialize(buffer, true) break case .updateBotPrecheckoutQuery(let flags, let queryId, let userId, let payload, let info, let shippingOptionId, let currency, let totalAmount): if boxed { - buffer.appendInt32(-1934976362) + buffer.appendInt32(1563376297) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(queryId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeBytes(payload, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)} @@ -5193,9 +5221,9 @@ public extension Api { break case .updateChannelReadMessagesContents(let channelId, let messages): if boxed { - buffer.appendInt32(1153291573) + buffer.appendInt32(-1987495099) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(messages.count)) for item in messages { @@ -5210,9 +5238,9 @@ public extension Api { break case .updateChannelAvailableMessages(let channelId, let availableMinId): if boxed { - buffer.appendInt32(-1304443240) + buffer.appendInt32(1893427255) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt32(availableMinId, buffer: buffer, boxed: false) break case .updateDialogUnreadMark(let flags, let peer): @@ -5306,10 +5334,10 @@ public extension Api { break case .updateMessagePollVote(let pollId, let userId, let options, let qts): if boxed { - buffer.appendInt32(274961865) + buffer.appendInt32(938909451) } serializeInt64(pollId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(options.count)) for item in options { @@ -5350,28 +5378,28 @@ public extension Api { break case .updateChannelMessageForwards(let channelId, let id, let forwards): if boxed { - buffer.appendInt32(-761649164) + buffer.appendInt32(1854571743) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(forwards, buffer: buffer, boxed: false) break case .updateReadChannelDiscussionInbox(let flags, let channelId, let topMsgId, let readMaxId, let broadcastId, let broadcastPost): if boxed { - buffer.appendInt32(-693004986) + buffer.appendInt32(482860628) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt32(topMsgId, buffer: buffer, boxed: false) serializeInt32(readMaxId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(broadcastId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(broadcastId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(broadcastPost!, buffer: buffer, boxed: false)} break case .updateReadChannelDiscussionOutbox(let channelId, let topMsgId, let readMaxId): if boxed { - buffer.appendInt32(1767677564) + buffer.appendInt32(1178116716) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt32(topMsgId, buffer: buffer, boxed: false) serializeInt32(readMaxId, buffer: buffer, boxed: false) break @@ -5384,10 +5412,10 @@ public extension Api { break case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let fromId, let action): if boxed { - buffer.appendInt32(-1937192669) + buffer.appendInt32(1796675352) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} fromId.serialize(buffer, true) action.serialize(buffer, true) @@ -5408,10 +5436,10 @@ public extension Api { break case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): if boxed { - buffer.appendInt32(1538885128) + buffer.appendInt32(-2054649973) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(messages.count)) for item in messages { @@ -5422,9 +5450,9 @@ public extension Api { break case .updateChat(let chatId): if boxed { - buffer.appendInt32(-124097970) + buffer.appendInt32(321954198) } - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) break case .updateGroupCallParticipants(let call, let participants, let version): if boxed { @@ -5440,9 +5468,9 @@ public extension Api { break case .updateGroupCall(let chatId, let call): if boxed { - buffer.appendInt32(347227392) + buffer.appendInt32(-1537295973) } - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) call.serialize(buffer, true) break case .updatePeerHistoryTTL(let flags, let peer, let ttlPeriod): @@ -5455,13 +5483,13 @@ public extension Api { break case .updateChatParticipant(let flags, let chatId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): if boxed { - buffer.appendInt32(-796432838) + buffer.appendInt32(-206342113) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(actorId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(actorId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {prevParticipant!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} @@ -5469,13 +5497,13 @@ public extension Api { break case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): if boxed { - buffer.appendInt32(-1738720581) + buffer.appendInt32(2146218476) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(actorId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(actorId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {prevParticipant!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} @@ -5483,9 +5511,9 @@ public extension Api { break case .updateBotStopped(let userId, let date, let stopped, let qts): if boxed { - buffer.appendInt32(-997782967) + buffer.appendInt32(133777546) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) stopped.serialize(buffer, true) serializeInt32(qts, buffer: buffer, boxed: false) @@ -5499,10 +5527,10 @@ public extension Api { break case .updateBotCommands(let peer, let botId, let commands): if boxed { - buffer.appendInt32(1299263278) + buffer.appendInt32(-813823885) } peer.serialize(buffer, true) - serializeInt64(botId, buffer: buffer, boxed: false) + serializeInt32(botId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(commands.count)) for item in commands { @@ -5756,8 +5784,8 @@ public extension Api { } } public static func parse_updateUserTyping(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Api.SendMessageAction? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.SendMessageAction @@ -5772,8 +5800,8 @@ public extension Api { } } public static func parse_updateChatUserTyping(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Api.Peer? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.Peer @@ -5806,8 +5834,8 @@ public extension Api { } } public static func parse_updateUserStatus(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Api.UserStatus? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.UserStatus @@ -5822,8 +5850,8 @@ public extension Api { } } public static func parse_updateUserName(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) var _3: String? @@ -5842,8 +5870,8 @@ public extension Api { } } public static func parse_updateUserPhoto(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() var _3: Api.UserProfilePhoto? @@ -5926,12 +5954,12 @@ public extension Api { } } public static func parse_updateChatParticipantAdd(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() var _5: Int32? @@ -5949,10 +5977,10 @@ public extension Api { } } public static func parse_updateChatParticipantDelete(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() let _c1 = _1 != nil @@ -6045,8 +6073,8 @@ public extension Api { } } public static func parse_updateUserPhone(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) let _c1 = _1 != nil @@ -6152,8 +6180,8 @@ public extension Api { public static func parse_updateChannelTooLong(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } let _c1 = _1 != nil @@ -6167,8 +6195,8 @@ public extension Api { } } public static func parse_updateChannel(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.Update.updateChannel(channelId: _1!) @@ -6201,8 +6229,8 @@ public extension Api { _1 = reader.readInt32() var _2: Int32? if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() var _5: Int32? @@ -6223,8 +6251,8 @@ public extension Api { } } public static func parse_updateDeleteChannelMessages(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: [Int32]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) @@ -6245,8 +6273,8 @@ public extension Api { } } public static func parse_updateChannelMessageViews(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() var _3: Int32? @@ -6262,10 +6290,10 @@ public extension Api { } } public static func parse_updateChatParticipantAdmin(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() var _3: Api.Bool? if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.Bool @@ -6323,8 +6351,8 @@ public extension Api { _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: String? _4 = parseString(reader) var _5: Api.GeoPoint? @@ -6354,8 +6382,8 @@ public extension Api { public static func parse_updateBotInlineSend(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: String? _3 = parseString(reader) var _4: Api.GeoPoint? @@ -6405,8 +6433,8 @@ public extension Api { _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: Api.Peer? if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Peer @@ -6458,8 +6486,8 @@ public extension Api { _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: Api.InputBotInlineMessageID? if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID @@ -6485,8 +6513,8 @@ public extension Api { } } public static func parse_updateReadChannelOutbox(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil @@ -6529,8 +6557,8 @@ public extension Api { return Api.Update.updatePtsChanged } public static func parse_updateChannelWebPage(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Api.WebPage? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.WebPage @@ -6623,8 +6651,8 @@ public extension Api { public static func parse_updateBotShippingQuery(_ reader: BufferReader) -> Update? { var _1: Int64? _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Buffer? _3 = parseBytes(reader) var _4: Api.PostAddress? @@ -6647,8 +6675,8 @@ public extension Api { _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: Buffer? _4 = parseBytes(reader) var _5: Api.PaymentRequestedInfo? @@ -6717,8 +6745,8 @@ public extension Api { return Api.Update.updateFavedStickers } public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: [Int32]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) @@ -6736,8 +6764,8 @@ public extension Api { return Api.Update.updateContactsReset } public static func parse_updateChannelAvailableMessages(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil @@ -6926,8 +6954,8 @@ public extension Api { public static func parse_updateMessagePollVote(_ reader: BufferReader) -> Update? { var _1: Int64? _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: [Buffer]? if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) @@ -6995,8 +7023,8 @@ public extension Api { } } public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() var _3: Int32? @@ -7014,14 +7042,14 @@ public extension Api { public static func parse_updateReadChannelDiscussionInbox(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } var _6: Int32? if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil @@ -7038,8 +7066,8 @@ public extension Api { } } public static func parse_updateReadChannelDiscussionOutbox(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() var _3: Int32? @@ -7075,8 +7103,8 @@ public extension Api { public static func parse_updateChannelUserTyping(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } var _4: Api.Peer? @@ -7129,8 +7157,8 @@ public extension Api { public static func parse_updatePinnedChannelMessages(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: [Int32]? if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) @@ -7152,8 +7180,8 @@ public extension Api { } } public static func parse_updateChat(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.Update.updateChat(chatId: _1!) @@ -7184,8 +7212,8 @@ public extension Api { } } public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Api.GroupCall? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.GroupCall @@ -7221,14 +7249,14 @@ public extension Api { public static func parse_updateChatParticipant(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - _5 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() var _6: Api.ChatParticipant? if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.ChatParticipant @@ -7262,14 +7290,14 @@ public extension Api { public static func parse_updateChannelParticipant(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - _5 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() var _6: Api.ChannelParticipant? if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant @@ -7301,8 +7329,8 @@ public extension Api { } } public static func parse_updateBotStopped(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() var _3: Api.Bool? @@ -7343,8 +7371,8 @@ public extension Api { if let signature = reader.readInt32() { _1 = Api.parse(reader, signature: signature) as? Api.Peer } - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: [Api.BotCommand]? if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) @@ -7484,58 +7512,58 @@ public extension Api { } public enum ChannelParticipant: TypeConstructorDescription { - case channelParticipant(userId: Int64, date: Int32) - case channelParticipantSelf(userId: Int64, inviterId: Int64, date: Int32) - case channelParticipantCreator(flags: Int32, userId: Int64, adminRights: Api.ChatAdminRights, rank: String?) - case channelParticipantAdmin(flags: Int32, userId: Int64, inviterId: Int64?, promotedBy: Int64, date: Int32, adminRights: Api.ChatAdminRights, rank: String?) - case channelParticipantBanned(flags: Int32, peer: Api.Peer, kickedBy: Int64, date: Int32, bannedRights: Api.ChatBannedRights) + case channelParticipant(userId: Int32, date: Int32) + case channelParticipantSelf(userId: Int32, inviterId: Int32, date: Int32) + case channelParticipantCreator(flags: Int32, userId: Int32, adminRights: Api.ChatAdminRights, rank: String?) + case channelParticipantAdmin(flags: Int32, userId: Int32, inviterId: Int32?, promotedBy: Int32, date: Int32, adminRights: Api.ChatAdminRights, rank: String?) + case channelParticipantBanned(flags: Int32, peer: Api.Peer, kickedBy: Int32, date: Int32, bannedRights: Api.ChatBannedRights) case channelParticipantLeft(peer: Api.Peer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .channelParticipant(let userId, let date): if boxed { - buffer.appendInt32(-1072953408) + buffer.appendInt32(367766557) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break case .channelParticipantSelf(let userId, let inviterId, let date): if boxed { - buffer.appendInt32(682146919) + buffer.appendInt32(-1557620115) } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt64(inviterId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(inviterId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break case .channelParticipantCreator(let flags, let userId, let adminRights, let rank): if boxed { - buffer.appendInt32(803602899) + buffer.appendInt32(1149094475) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) adminRights.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeString(rank!, buffer: buffer, boxed: false)} break case .channelParticipantAdmin(let flags, let userId, let inviterId, let promotedBy, let date, let adminRights, let rank): if boxed { - buffer.appendInt32(885242707) + buffer.appendInt32(-859915345) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt64(inviterId!, buffer: buffer, boxed: false)} - serializeInt64(promotedBy, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(inviterId!, buffer: buffer, boxed: false)} + serializeInt32(promotedBy, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) adminRights.serialize(buffer, true) if Int(flags) & Int(1 << 2) != 0 {serializeString(rank!, buffer: buffer, boxed: false)} break case .channelParticipantBanned(let flags, let peer, let kickedBy, let date, let bannedRights): if boxed { - buffer.appendInt32(1844969806) + buffer.appendInt32(1352785878) } serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) - serializeInt64(kickedBy, buffer: buffer, boxed: false) + serializeInt32(kickedBy, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) bannedRights.serialize(buffer, true) break @@ -7566,8 +7594,8 @@ public extension Api { } public static func parse_channelParticipant(_ reader: BufferReader) -> ChannelParticipant? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil @@ -7580,10 +7608,10 @@ public extension Api { } } public static func parse_channelParticipantSelf(_ reader: BufferReader) -> ChannelParticipant? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() let _c1 = _1 != nil @@ -7599,8 +7627,8 @@ public extension Api { public static func parse_channelParticipantCreator(_ reader: BufferReader) -> ChannelParticipant? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Api.ChatAdminRights? if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights @@ -7621,12 +7649,12 @@ public extension Api { public static func parse_channelParticipantAdmin(_ reader: BufferReader) -> ChannelParticipant? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt64() } - var _4: Int64? - _4 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } + var _4: Int32? + _4 = reader.readInt32() var _5: Int32? _5 = reader.readInt32() var _6: Api.ChatAdminRights? @@ -7656,8 +7684,8 @@ public extension Api { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.Peer } - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() var _5: Api.ChatBannedRights? @@ -7692,32 +7720,32 @@ public extension Api { } public enum MessageUserVote: TypeConstructorDescription { - case messageUserVote(userId: Int64, option: Buffer, date: Int32) - case messageUserVoteInputOption(userId: Int64, date: Int32) - case messageUserVoteMultiple(userId: Int64, options: [Buffer], date: Int32) + case messageUserVote(userId: Int32, option: Buffer, date: Int32) + case messageUserVoteInputOption(userId: Int32, date: Int32) + case messageUserVoteMultiple(userId: Int32, options: [Buffer], date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .messageUserVote(let userId, let option, let date): if boxed { - buffer.appendInt32(886196148) + buffer.appendInt32(-1567730343) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeBytes(option, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break case .messageUserVoteInputOption(let userId, let date): if boxed { - buffer.appendInt32(1017491692) + buffer.appendInt32(909603888) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break case .messageUserVoteMultiple(let userId, let options, let date): if boxed { - buffer.appendInt32(-1973033641) + buffer.appendInt32(244310238) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(options.count)) for item in options { @@ -7740,8 +7768,8 @@ public extension Api { } public static func parse_messageUserVote(_ reader: BufferReader) -> MessageUserVote? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Buffer? _2 = parseBytes(reader) var _3: Int32? @@ -7757,8 +7785,8 @@ public extension Api { } } public static func parse_messageUserVoteInputOption(_ reader: BufferReader) -> MessageUserVote? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil @@ -7771,8 +7799,8 @@ public extension Api { } } public static func parse_messageUserVoteMultiple(_ reader: BufferReader) -> MessageUserVote? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: [Buffer]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) @@ -8146,15 +8174,15 @@ public extension Api { } public enum ContactStatus: TypeConstructorDescription { - case contactStatus(userId: Int64, status: Api.UserStatus) + case contactStatus(userId: Int32, status: Api.UserStatus) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .contactStatus(let userId, let status): if boxed { - buffer.appendInt32(383348795) + buffer.appendInt32(-748155807) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) status.serialize(buffer, true) break } @@ -8168,8 +8196,8 @@ public extension Api { } public static func parse_contactStatus(_ reader: BufferReader) -> ContactStatus? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Api.UserStatus? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.UserStatus @@ -9830,15 +9858,15 @@ public extension Api { } public enum ChatAdminWithInvites: TypeConstructorDescription { - case chatAdminWithInvites(adminId: Int64, invitesCount: Int32, revokedInvitesCount: Int32) + case chatAdminWithInvites(adminId: Int32, invitesCount: Int32, revokedInvitesCount: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .chatAdminWithInvites(let adminId, let invitesCount, let revokedInvitesCount): if boxed { - buffer.appendInt32(-219353309) + buffer.appendInt32(-539872497) } - serializeInt64(adminId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) serializeInt32(invitesCount, buffer: buffer, boxed: false) serializeInt32(revokedInvitesCount, buffer: buffer, boxed: false) break @@ -9853,8 +9881,8 @@ public extension Api { } public static func parse_chatAdminWithInvites(_ reader: BufferReader) -> ChatAdminWithInvites? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() var _3: Int32? @@ -10578,11 +10606,11 @@ public extension Api { indirect public enum InputPeer: TypeConstructorDescription { case inputPeerEmpty case inputPeerSelf - case inputPeerChat(chatId: Int64) - case inputPeerUser(userId: Int64, accessHash: Int64) - case inputPeerChannel(channelId: Int64, accessHash: Int64) - case inputPeerUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64) - case inputPeerChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int64) + case inputPeerChat(chatId: Int32) + case inputPeerUser(userId: Int32, accessHash: Int64) + case inputPeerChannel(channelId: Int32, accessHash: Int64) + case inputPeerUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int32) + case inputPeerChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -10600,39 +10628,39 @@ public extension Api { break case .inputPeerChat(let chatId): if boxed { - buffer.appendInt32(900291769) + buffer.appendInt32(396093539) } - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) break case .inputPeerUser(let userId, let accessHash): if boxed { - buffer.appendInt32(-571955892) + buffer.appendInt32(2072935910) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) break case .inputPeerChannel(let channelId, let accessHash): if boxed { - buffer.appendInt32(666680316) + buffer.appendInt32(548253432) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) break case .inputPeerUserFromMessage(let peer, let msgId, let userId): if boxed { - buffer.appendInt32(-1468331492) + buffer.appendInt32(398123750) } peer.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) break case .inputPeerChannelFromMessage(let peer, let msgId, let channelId): if boxed { - buffer.appendInt32(-1121318848) + buffer.appendInt32(-1667893317) } peer.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) break } } @@ -10663,8 +10691,8 @@ public extension Api { return Api.InputPeer.inputPeerSelf } public static func parse_inputPeerChat(_ reader: BufferReader) -> InputPeer? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.InputPeer.inputPeerChat(chatId: _1!) @@ -10674,8 +10702,8 @@ public extension Api { } } public static func parse_inputPeerUser(_ reader: BufferReader) -> InputPeer? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() let _c1 = _1 != nil @@ -10688,8 +10716,8 @@ public extension Api { } } public static func parse_inputPeerChannel(_ reader: BufferReader) -> InputPeer? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() let _c1 = _1 != nil @@ -10708,8 +10736,8 @@ public extension Api { } var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -10727,8 +10755,8 @@ public extension Api { } var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -10742,15 +10770,15 @@ public extension Api { } public enum Contact: TypeConstructorDescription { - case contact(userId: Int64, mutual: Api.Bool) + case contact(userId: Int32, mutual: Api.Bool) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .contact(let userId, let mutual): if boxed { - buffer.appendInt32(341499403) + buffer.appendInt32(-116274796) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) mutual.serialize(buffer, true) break } @@ -10764,8 +10792,8 @@ public extension Api { } public static func parse_contact(_ reader: BufferReader) -> Contact? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Api.Bool? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.Bool @@ -11100,8 +11128,8 @@ public extension Api { case inputPrivacyValueDisallowContacts case inputPrivacyValueDisallowAll case inputPrivacyValueDisallowUsers(users: [Api.InputUser]) - case inputPrivacyValueAllowChatParticipants(chats: [Int64]) - case inputPrivacyValueDisallowChatParticipants(chats: [Int64]) + case inputPrivacyValueAllowChatParticipants(chats: [Int32]) + case inputPrivacyValueDisallowChatParticipants(chats: [Int32]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -11151,22 +11179,22 @@ public extension Api { break case .inputPrivacyValueAllowChatParticipants(let chats): if boxed { - buffer.appendInt32(-2079962673) + buffer.appendInt32(1283572154) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break case .inputPrivacyValueDisallowChatParticipants(let chats): if boxed { - buffer.appendInt32(-380694650) + buffer.appendInt32(-668769361) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break } @@ -11232,9 +11260,9 @@ public extension Api { } } public static func parse_inputPrivacyValueAllowChatParticipants(_ reader: BufferReader) -> InputPrivacyRule? { - var _1: [Int64]? + var _1: [Int32]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil if _c1 { @@ -11245,9 +11273,9 @@ public extension Api { } } public static func parse_inputPrivacyValueDisallowChatParticipants(_ reader: BufferReader) -> InputPrivacyRule? { - var _1: [Int64]? + var _1: [Int32]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil if _c1 { @@ -11278,7 +11306,7 @@ public extension Api { case channelAdminLogEventActionTogglePreHistoryHidden(newValue: Api.Bool) case channelAdminLogEventActionDefaultBannedRights(prevBannedRights: Api.ChatBannedRights, newBannedRights: Api.ChatBannedRights) case channelAdminLogEventActionStopPoll(message: Api.Message) - case channelAdminLogEventActionChangeLinkedChat(prevValue: Int64, newValue: Int64) + case channelAdminLogEventActionChangeLinkedChat(prevValue: Int32, newValue: Int32) case channelAdminLogEventActionChangeLocation(prevValue: Api.ChannelLocation, newValue: Api.ChannelLocation) case channelAdminLogEventActionToggleSlowMode(prevValue: Int32, newValue: Int32) case channelAdminLogEventActionStartGroupCall(call: Api.InputGroupCall) @@ -11414,10 +11442,10 @@ public extension Api { break case .channelAdminLogEventActionChangeLinkedChat(let prevValue, let newValue): if boxed { - buffer.appendInt32(84703944) + buffer.appendInt32(-1569748965) } - serializeInt64(prevValue, buffer: buffer, boxed: false) - serializeInt64(newValue, buffer: buffer, boxed: false) + serializeInt32(prevValue, buffer: buffer, boxed: false) + serializeInt32(newValue, buffer: buffer, boxed: false) break case .channelAdminLogEventActionChangeLocation(let prevValue, let newValue): if boxed { @@ -11821,10 +11849,10 @@ public extension Api { } } public static func parse_channelAdminLogEventActionChangeLinkedChat(_ reader: BufferReader) -> ChannelAdminLogEventAction? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { @@ -12676,15 +12704,15 @@ public extension Api { } public enum StatsGroupTopPoster: TypeConstructorDescription { - case statsGroupTopPoster(userId: Int64, messages: Int32, avgChars: Int32) + case statsGroupTopPoster(userId: Int32, messages: Int32, avgChars: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .statsGroupTopPoster(let userId, let messages, let avgChars): if boxed { - buffer.appendInt32(-1660637285) + buffer.appendInt32(418631927) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(messages, buffer: buffer, boxed: false) serializeInt32(avgChars, buffer: buffer, boxed: false) break @@ -12699,8 +12727,8 @@ public extension Api { } public static func parse_statsGroupTopPoster(_ reader: BufferReader) -> StatsGroupTopPoster? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() var _3: Int32? @@ -13516,16 +13544,16 @@ public extension Api { } public enum HighScore: TypeConstructorDescription { - case highScore(pos: Int32, userId: Int64, score: Int32) + case highScore(pos: Int32, userId: Int32, score: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .highScore(let pos, let userId, let score): if boxed { - buffer.appendInt32(1940093419) + buffer.appendInt32(1493171408) } serializeInt32(pos, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(score, buffer: buffer, boxed: false) break } @@ -13541,8 +13569,8 @@ public extension Api { public static func parse_highScore(_ reader: BufferReader) -> HighScore? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() let _c1 = _1 != nil @@ -13884,8 +13912,8 @@ public extension Api { public enum InputUser: TypeConstructorDescription { case inputUserEmpty case inputUserSelf - case inputUser(userId: Int64, accessHash: Int64) - case inputUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64) + case inputUser(userId: Int32, accessHash: Int64) + case inputUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -13903,18 +13931,18 @@ public extension Api { break case .inputUser(let userId, let accessHash): if boxed { - buffer.appendInt32(-233744186) + buffer.appendInt32(-668391402) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) break case .inputUserFromMessage(let peer, let msgId, let userId): if boxed { - buffer.appendInt32(497305826) + buffer.appendInt32(756118935) } peer.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) break } } @@ -13939,8 +13967,8 @@ public extension Api { return Api.InputUser.inputUserSelf } public static func parse_inputUser(_ reader: BufferReader) -> InputUser? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() let _c1 = _1 != nil @@ -13959,8 +13987,8 @@ public extension Api { } var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -14688,15 +14716,15 @@ public extension Api { } public enum ChatInviteImporter: TypeConstructorDescription { - case chatInviteImporter(userId: Int64, date: Int32) + case chatInviteImporter(userId: Int32, date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .chatInviteImporter(let userId, let date): if boxed { - buffer.appendInt32(190633460) + buffer.appendInt32(507405952) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) break } @@ -14710,8 +14738,8 @@ public extension Api { } public static func parse_chatInviteImporter(_ reader: BufferReader) -> ChatInviteImporter? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil @@ -14764,13 +14792,13 @@ public extension Api { } public enum MessageReplies: TypeConstructorDescription { - case messageReplies(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Api.Peer]?, channelId: Int64?, maxId: Int32?, readMaxId: Int32?) + case messageReplies(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Api.Peer]?, channelId: Int32?, maxId: Int32?, readMaxId: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): if boxed { - buffer.appendInt32(-2083123262) + buffer.appendInt32(1093204652) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(replies, buffer: buffer, boxed: false) @@ -14780,7 +14808,7 @@ public extension Api { for item in recentRepliers! { item.serialize(buffer, true) }} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(channelId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt32(readMaxId!, buffer: buffer, boxed: false)} break @@ -14805,8 +14833,8 @@ public extension Api { if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } } - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } var _6: Int32? if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt32() } var _7: Int32? @@ -15611,8 +15639,8 @@ public extension Api { } public enum Updates: TypeConstructorDescription { case updatesTooLong - case updateShortMessage(flags: Int32, id: Int32, userId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) - case updateShortChatMessage(flags: Int32, id: Int32, fromId: Int64, chatId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) + case updateShortMessage(flags: Int32, id: Int32, userId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) + case updateShortChatMessage(flags: Int32, id: Int32, fromId: Int32, chatId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) case updateShort(update: Api.Update, date: Int32) case updatesCombined(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seqStart: Int32, seq: Int32) case updates(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seq: Int32) @@ -15628,17 +15656,17 @@ public extension Api { break case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): if boxed { - buffer.appendInt32(826001400) + buffer.appendInt32(-84936653) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeString(message, buffer: buffer, boxed: false) serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(ptsCount, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) buffer.appendInt32(Int32(entities!.count)) @@ -15649,18 +15677,18 @@ public extension Api { break case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities, let ttlPeriod): if boxed { - buffer.appendInt32(1299050149) + buffer.appendInt32(290961496) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) - serializeInt64(fromId, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(fromId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) serializeString(message, buffer: buffer, boxed: false) serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(ptsCount, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) buffer.appendInt32(Int32(entities!.count)) @@ -15768,8 +15796,8 @@ public extension Api { _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: String? _4 = parseString(reader) var _5: Int32? @@ -15782,8 +15810,8 @@ public extension Api { if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader } } - var _9: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt64() } + var _9: Int32? + if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt32() } var _10: Api.MessageReplyHeader? if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { _10 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader @@ -15818,10 +15846,10 @@ public extension Api { _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() var _5: String? _5 = parseString(reader) var _6: Int32? @@ -15834,8 +15862,8 @@ public extension Api { if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader } } - var _10: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt64() } + var _10: Int32? + if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() } var _11: Api.MessageReplyHeader? if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader @@ -16023,7 +16051,7 @@ public extension Api { case messageMediaEmpty case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?) case messageMediaGeo(geo: Api.GeoPoint) - case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64) + case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int32) case messageMediaUnsupported case messageMediaDocument(flags: Int32, document: Api.Document?, ttlSeconds: Int32?) case messageMediaWebPage(webpage: Api.WebPage) @@ -16058,13 +16086,13 @@ public extension Api { break case .messageMediaContact(let phoneNumber, let firstName, let lastName, let vcard, let userId): if boxed { - buffer.appendInt32(1882335561) + buffer.appendInt32(-873313984) } serializeString(phoneNumber, buffer: buffer, boxed: false) serializeString(firstName, buffer: buffer, boxed: false) serializeString(lastName, buffer: buffer, boxed: false) serializeString(vcard, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) break case .messageMediaUnsupported: if boxed { @@ -16218,8 +16246,8 @@ public extension Api { _3 = parseString(reader) var _4: String? _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -16942,15 +16970,15 @@ public extension Api { } public enum BotInfo: TypeConstructorDescription { - case botInfo(userId: Int64, description: String, commands: [Api.BotCommand]) + case botInfo(userId: Int32, description: String, commands: [Api.BotCommand]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .botInfo(let userId, let description, let commands): if boxed { - buffer.appendInt32(460632885) + buffer.appendInt32(-1729618630) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeString(description, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(commands.count)) @@ -16969,8 +16997,8 @@ public extension Api { } public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) var _3: [Api.BotCommand]? @@ -16990,23 +17018,23 @@ public extension Api { } public enum User: TypeConstructorDescription { - case userEmpty(id: Int64) - case user(flags: Int32, id: Int64, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: Api.UserProfilePhoto?, status: Api.UserStatus?, botInfoVersion: Int32?, restrictionReason: [Api.RestrictionReason]?, botInlinePlaceholder: String?, langCode: String?) + case userEmpty(id: Int32) + case user(flags: Int32, id: Int32, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: Api.UserProfilePhoto?, status: Api.UserStatus?, botInfoVersion: Int32?, restrictionReason: [Api.RestrictionReason]?, botInlinePlaceholder: String?, langCode: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .userEmpty(let id): if boxed { - buffer.appendInt32(-742634630) + buffer.appendInt32(537022650) } - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) break case .user(let flags, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode): if boxed { - buffer.appendInt32(1073147056) + buffer.appendInt32(-1820043071) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(accessHash!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeString(firstName!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeString(lastName!, buffer: buffer, boxed: false)} @@ -17036,8 +17064,8 @@ public extension Api { } public static func parse_userEmpty(_ reader: BufferReader) -> User? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.User.userEmpty(id: _1!) @@ -17049,8 +17077,8 @@ public extension Api { public static func parse_user(_ reader: BufferReader) -> User? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Int64? if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt64() } var _4: String? @@ -17103,7 +17131,7 @@ public extension Api { } public enum Message: TypeConstructorDescription { case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) - case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?) + case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, ttlPeriod: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -17118,14 +17146,14 @@ public extension Api { break case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason, let ttlPeriod): if boxed { - buffer.appendInt32(-2049520670) + buffer.appendInt32(-1125940270) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} peerId.serialize(buffer, true) if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt64(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} serializeInt32(date, buffer: buffer, boxed: false) serializeString(message, buffer: buffer, boxed: false) @@ -17212,8 +17240,8 @@ public extension Api { if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader } } - var _6: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_6 = reader.readInt64() } + var _6: Int32? + if Int(_1!) & Int(1 << 11) != 0 {_6 = reader.readInt32() } var _7: Api.MessageReplyHeader? if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader @@ -17324,15 +17352,15 @@ public extension Api { } public enum StatsGroupTopInviter: TypeConstructorDescription { - case statsGroupTopInviter(userId: Int64, invitations: Int32) + case statsGroupTopInviter(userId: Int32, invitations: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .statsGroupTopInviter(let userId, let invitations): if boxed { - buffer.appendInt32(1398765469) + buffer.appendInt32(831924812) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(invitations, buffer: buffer, boxed: false) break } @@ -17346,8 +17374,8 @@ public extension Api { } public static func parse_statsGroupTopInviter(_ reader: BufferReader) -> StatsGroupTopInviter? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil @@ -17371,7 +17399,7 @@ public extension Api { case inputPhotoLegacyFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, volumeId: Int64, localId: Int32, secret: Int64) case inputPeerPhotoFileLocation(flags: Int32, peer: Api.InputPeer, photoId: Int64) case inputStickerSetThumb(stickerset: Api.InputStickerSet, thumbVersion: Int32) - case inputGroupCallStream(call: Api.InputGroupCall, timeMs: Int64, scale: Int32) + case inputGroupCallStream(flags: Int32, call: Api.InputGroupCall, timeMs: Int64, scale: Int32, videoChannel: Int32?, videoQuality: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -17448,13 +17476,16 @@ public extension Api { stickerset.serialize(buffer, true) serializeInt32(thumbVersion, buffer: buffer, boxed: false) break - case .inputGroupCallStream(let call, let timeMs, let scale): + case .inputGroupCallStream(let flags, let call, let timeMs, let scale, let videoChannel, let videoQuality): if boxed { - buffer.appendInt32(-1146808775) + buffer.appendInt32(93890858) } + serializeInt32(flags, buffer: buffer, boxed: false) call.serialize(buffer, true) serializeInt64(timeMs, buffer: buffer, boxed: false) serializeInt32(scale, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(videoChannel!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(videoQuality!, buffer: buffer, boxed: false)} break } } @@ -17479,8 +17510,8 @@ public extension Api { return ("inputPeerPhotoFileLocation", [("flags", flags), ("peer", peer), ("photoId", photoId)]) case .inputStickerSetThumb(let stickerset, let thumbVersion): return ("inputStickerSetThumb", [("stickerset", stickerset), ("thumbVersion", thumbVersion)]) - case .inputGroupCallStream(let call, let timeMs, let scale): - return ("inputGroupCallStream", [("call", call), ("timeMs", timeMs), ("scale", scale)]) + case .inputGroupCallStream(let flags, let call, let timeMs, let scale, let videoChannel, let videoQuality): + return ("inputGroupCallStream", [("flags", flags), ("call", call), ("timeMs", timeMs), ("scale", scale), ("videoChannel", videoChannel), ("videoQuality", videoQuality)]) } } @@ -17637,19 +17668,28 @@ public extension Api { } } public static func parse_inputGroupCallStream(_ reader: BufferReader) -> InputFileLocation? { - var _1: Api.InputGroupCall? + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGroupCall? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputFileLocation.inputGroupCallStream(call: _1!, timeMs: _2!, scale: _3!) + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputFileLocation.inputGroupCallStream(flags: _1!, call: _2!, timeMs: _3!, scale: _4!, videoChannel: _5, videoQuality: _6) } else { return nil @@ -17796,24 +17836,24 @@ public extension Api { } public enum ChatParticipants: TypeConstructorDescription { - case chatParticipantsForbidden(flags: Int32, chatId: Int64, selfParticipant: Api.ChatParticipant?) - case chatParticipants(chatId: Int64, participants: [Api.ChatParticipant], version: Int32) + case chatParticipantsForbidden(flags: Int32, chatId: Int32, selfParticipant: Api.ChatParticipant?) + case chatParticipants(chatId: Int32, participants: [Api.ChatParticipant], version: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .chatParticipantsForbidden(let flags, let chatId, let selfParticipant): if boxed { - buffer.appendInt32(-2023500831) + buffer.appendInt32(-57668565) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {selfParticipant!.serialize(buffer, true)} break case .chatParticipants(let chatId, let participants, let version): if boxed { - buffer.appendInt32(1018991608) + buffer.appendInt32(1061556205) } - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(participants.count)) for item in participants { @@ -17836,8 +17876,8 @@ public extension Api { public static func parse_chatParticipantsForbidden(_ reader: BufferReader) -> ChatParticipants? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: Api.ChatParticipant? if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.ChatParticipant @@ -17853,8 +17893,8 @@ public extension Api { } } public static func parse_chatParticipants(_ reader: BufferReader) -> ChatParticipants? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: [Api.ChatParticipant]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatParticipant.self) @@ -18386,19 +18426,22 @@ public extension Api { } public enum InputThemeSettings: TypeConstructorDescription { - case inputThemeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, messageTopColor: Int32?, messageBottomColor: Int32?, wallpaper: Api.InputWallPaper?, wallpaperSettings: Api.WallPaperSettings?) + case inputThemeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, messageColors: [Int32]?, wallpaper: Api.InputWallPaper?, wallpaperSettings: Api.WallPaperSettings?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inputThemeSettings(let flags, let baseTheme, let accentColor, let messageTopColor, let messageBottomColor, let wallpaper, let wallpaperSettings): + case .inputThemeSettings(let flags, let baseTheme, let accentColor, let messageColors, let wallpaper, let wallpaperSettings): if boxed { - buffer.appendInt32(-1118798639) + buffer.appendInt32(-13043438) } serializeInt32(flags, buffer: buffer, boxed: false) baseTheme.serialize(buffer, true) serializeInt32(accentColor, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(messageTopColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(messageBottomColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messageColors!.count)) + for item in messageColors! { + serializeInt32(item, buffer: buffer, boxed: false) + }} if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {wallpaperSettings!.serialize(buffer, true)} break @@ -18407,8 +18450,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inputThemeSettings(let flags, let baseTheme, let accentColor, let messageTopColor, let messageBottomColor, let wallpaper, let wallpaperSettings): - return ("inputThemeSettings", [("flags", flags), ("baseTheme", baseTheme), ("accentColor", accentColor), ("messageTopColor", messageTopColor), ("messageBottomColor", messageBottomColor), ("wallpaper", wallpaper), ("wallpaperSettings", wallpaperSettings)]) + case .inputThemeSettings(let flags, let baseTheme, let accentColor, let messageColors, let wallpaper, let wallpaperSettings): + return ("inputThemeSettings", [("flags", flags), ("baseTheme", baseTheme), ("accentColor", accentColor), ("messageColors", messageColors), ("wallpaper", wallpaper), ("wallpaperSettings", wallpaperSettings)]) } } @@ -18421,27 +18464,26 @@ public extension Api { } var _3: Int32? _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } - var _6: Api.InputWallPaper? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.InputWallPaper + var _4: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } } - var _7: Api.WallPaperSettings? + var _5: Api.InputWallPaper? if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings + _5 = Api.parse(reader, signature: signature) as? Api.InputWallPaper + } } + var _6: Api.WallPaperSettings? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.InputThemeSettings.inputThemeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, messageTopColor: _4, messageBottomColor: _5, wallpaper: _6, wallpaperSettings: _7) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputThemeSettings.inputThemeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, messageColors: _4, wallpaper: _5, wallpaperSettings: _6) } else { return nil @@ -18556,6 +18598,52 @@ public extension Api { } } + } + public enum ChatTheme: TypeConstructorDescription { + case chatTheme(emoticon: String, theme: Api.Theme, darkTheme: Api.Theme) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chatTheme(let emoticon, let theme, let darkTheme): + if boxed { + buffer.appendInt32(-318022605) + } + serializeString(emoticon, buffer: buffer, boxed: false) + theme.serialize(buffer, true) + darkTheme.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .chatTheme(let emoticon, let theme, let darkTheme): + return ("chatTheme", [("emoticon", emoticon), ("theme", theme), ("darkTheme", darkTheme)]) + } + } + + public static func parse_chatTheme(_ reader: BufferReader) -> ChatTheme? { + var _1: String? + _1 = parseString(reader) + var _2: Api.Theme? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Theme + } + var _3: Api.Theme? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Theme + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.ChatTheme.chatTheme(emoticon: _1!, theme: _2!, darkTheme: _3!) + } + else { + return nil + } + } + } public enum PhoneCallDiscardReason: TypeConstructorDescription { case phoneCallDiscardReasonMissed @@ -18754,17 +18842,17 @@ public extension Api { } public enum ChannelAdminLogEvent: TypeConstructorDescription { - case channelAdminLogEvent(id: Int64, date: Int32, userId: Int64, action: Api.ChannelAdminLogEventAction) + case channelAdminLogEvent(id: Int64, date: Int32, userId: Int32, action: Api.ChannelAdminLogEventAction) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .channelAdminLogEvent(let id, let date, let userId, let action): if boxed { - buffer.appendInt32(531458253) + buffer.appendInt32(995769920) } serializeInt64(id, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) action.serialize(buffer, true) break } @@ -18782,8 +18870,8 @@ public extension Api { _1 = reader.readInt64() var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: Api.ChannelAdminLogEventAction? if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.ChannelAdminLogEventAction @@ -19128,6 +19216,76 @@ public extension Api { } } + } + public enum SponsoredMessage: TypeConstructorDescription { + case sponsoredMessage(flags: Int32, randomId: Buffer, peerId: Api.Peer, fromId: Api.Peer, message: String, media: Api.MessageMedia?, entities: [Api.MessageEntity]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sponsoredMessage(let flags, let randomId, let peerId, let fromId, let message, let media, let entities): + if boxed { + buffer.appendInt32(-160304943) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeBytes(randomId, buffer: buffer, boxed: false) + peerId.serialize(buffer, true) + fromId.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .sponsoredMessage(let flags, let randomId, let peerId, let fromId, let message, let media, let entities): + return ("sponsoredMessage", [("flags", flags), ("randomId", randomId), ("peerId", peerId), ("fromId", fromId), ("message", message), ("media", media), ("entities", entities)]) + } + } + + public static func parse_sponsoredMessage(_ reader: BufferReader) -> SponsoredMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Api.Peer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: String? + _5 = parseString(reader) + var _6: Api.MessageMedia? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } } + var _7: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, peerId: _3!, fromId: _4!, message: _5!, media: _6, entities: _7) + } + else { + return nil + } + } + } public enum BaseTheme: TypeConstructorDescription { case baseThemeClassic @@ -19838,17 +19996,17 @@ public extension Api { } public enum ExportedChatInvite: TypeConstructorDescription { - case chatInviteExported(flags: Int32, link: String, adminId: Int64, date: Int32, startDate: Int32?, expireDate: Int32?, usageLimit: Int32?, usage: Int32?) + case chatInviteExported(flags: Int32, link: String, adminId: Int32, date: Int32, startDate: Int32?, expireDate: Int32?, usageLimit: Int32?, usage: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage): if boxed { - buffer.appendInt32(-1316944408) + buffer.appendInt32(1847917725) } serializeInt32(flags, buffer: buffer, boxed: false) serializeString(link, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 4) != 0 {serializeInt32(startDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(expireDate!, buffer: buffer, boxed: false)} @@ -19870,8 +20028,8 @@ public extension Api { _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() var _5: Int32? @@ -20572,12 +20730,12 @@ public extension Api { public enum PrivacyRule: TypeConstructorDescription { case privacyValueAllowContacts case privacyValueAllowAll - case privacyValueAllowUsers(users: [Int64]) + case privacyValueAllowUsers(users: [Int32]) case privacyValueDisallowContacts case privacyValueDisallowAll - case privacyValueDisallowUsers(users: [Int64]) - case privacyValueAllowChatParticipants(chats: [Int64]) - case privacyValueDisallowChatParticipants(chats: [Int64]) + case privacyValueDisallowUsers(users: [Int32]) + case privacyValueAllowChatParticipants(chats: [Int32]) + case privacyValueDisallowChatParticipants(chats: [Int32]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -20595,12 +20753,12 @@ public extension Api { break case .privacyValueAllowUsers(let users): if boxed { - buffer.appendInt32(-1198497870) + buffer.appendInt32(1297858060) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break case .privacyValueDisallowContacts: @@ -20617,32 +20775,32 @@ public extension Api { break case .privacyValueDisallowUsers(let users): if boxed { - buffer.appendInt32(-463335103) + buffer.appendInt32(209668535) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break case .privacyValueAllowChatParticipants(let chats): if boxed { - buffer.appendInt32(1796427406) + buffer.appendInt32(415136107) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break case .privacyValueDisallowChatParticipants(let chats): if boxed { - buffer.appendInt32(1103656293) + buffer.appendInt32(-1397881200) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(chats.count)) for item in chats { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break } @@ -20676,9 +20834,9 @@ public extension Api { return Api.PrivacyRule.privacyValueAllowAll } public static func parse_privacyValueAllowUsers(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? + var _1: [Int32]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil if _c1 { @@ -20695,9 +20853,9 @@ public extension Api { return Api.PrivacyRule.privacyValueDisallowAll } public static func parse_privacyValueDisallowUsers(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? + var _1: [Int32]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil if _c1 { @@ -20708,9 +20866,9 @@ public extension Api { } } public static func parse_privacyValueAllowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? + var _1: [Int32]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil if _c1 { @@ -20721,9 +20879,9 @@ public extension Api { } } public static func parse_privacyValueDisallowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? + var _1: [Int32]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil if _c1 { @@ -20737,16 +20895,16 @@ public extension Api { } public enum MessageAction: TypeConstructorDescription { case messageActionEmpty - case messageActionChatCreate(title: String, users: [Int64]) + case messageActionChatCreate(title: String, users: [Int32]) case messageActionChatEditTitle(title: String) case messageActionChatEditPhoto(photo: Api.Photo) case messageActionChatDeletePhoto - case messageActionChatAddUser(users: [Int64]) - case messageActionChatDeleteUser(userId: Int64) - case messageActionChatJoinedByLink(inviterId: Int64) + case messageActionChatAddUser(users: [Int32]) + case messageActionChatDeleteUser(userId: Int32) + case messageActionChatJoinedByLink(inviterId: Int32) case messageActionChannelCreate(title: String) - case messageActionChatMigrateTo(channelId: Int64) - case messageActionChannelMigrateFrom(title: String, chatId: Int64) + case messageActionChatMigrateTo(channelId: Int32) + case messageActionChannelMigrateFrom(title: String, chatId: Int32) case messageActionPinMessage case messageActionHistoryClear case messageActionGameScore(gameId: Int64, score: Int32) @@ -20761,9 +20919,10 @@ public extension Api { case messageActionContactSignUp case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32) case messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?) - case messageActionInviteToGroupCall(call: Api.InputGroupCall, users: [Int64]) + case messageActionInviteToGroupCall(call: Api.InputGroupCall, users: [Int32]) case messageActionSetMessagesTTL(period: Int32) case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32) + case messageActionSetChatTheme(emoticon: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -20775,13 +20934,13 @@ public extension Api { break case .messageActionChatCreate(let title, let users): if boxed { - buffer.appendInt32(-1119368275) + buffer.appendInt32(-1503425638) } serializeString(title, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break case .messageActionChatEditTitle(let title): @@ -20804,25 +20963,25 @@ public extension Api { break case .messageActionChatAddUser(let users): if boxed { - buffer.appendInt32(365886720) + buffer.appendInt32(1217033015) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break case .messageActionChatDeleteUser(let userId): if boxed { - buffer.appendInt32(-1539362612) + buffer.appendInt32(-1297179892) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) break case .messageActionChatJoinedByLink(let inviterId): if boxed { - buffer.appendInt32(51520707) + buffer.appendInt32(-123931160) } - serializeInt64(inviterId, buffer: buffer, boxed: false) + serializeInt32(inviterId, buffer: buffer, boxed: false) break case .messageActionChannelCreate(let title): if boxed { @@ -20832,16 +20991,16 @@ public extension Api { break case .messageActionChatMigrateTo(let channelId): if boxed { - buffer.appendInt32(-519864430) + buffer.appendInt32(1371385889) } - serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt32(channelId, buffer: buffer, boxed: false) break case .messageActionChannelMigrateFrom(let title, let chatId): if boxed { - buffer.appendInt32(-365344535) + buffer.appendInt32(-1336546578) } serializeString(title, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) break case .messageActionPinMessage: if boxed { @@ -20953,13 +21112,13 @@ public extension Api { break case .messageActionInviteToGroupCall(let call, let users): if boxed { - buffer.appendInt32(1345295095) + buffer.appendInt32(1991897370) } call.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { - serializeInt64(item, buffer: buffer, boxed: false) + serializeInt32(item, buffer: buffer, boxed: false) } break case .messageActionSetMessagesTTL(let period): @@ -20975,6 +21134,12 @@ public extension Api { call.serialize(buffer, true) serializeInt32(scheduleDate, buffer: buffer, boxed: false) break + case .messageActionSetChatTheme(let emoticon): + if boxed { + buffer.appendInt32(-1434950843) + } + serializeString(emoticon, buffer: buffer, boxed: false) + break } } @@ -21036,6 +21201,8 @@ public extension Api { return ("messageActionSetMessagesTTL", [("period", period)]) case .messageActionGroupCallScheduled(let call, let scheduleDate): return ("messageActionGroupCallScheduled", [("call", call), ("scheduleDate", scheduleDate)]) + case .messageActionSetChatTheme(let emoticon): + return ("messageActionSetChatTheme", [("emoticon", emoticon)]) } } @@ -21045,9 +21212,9 @@ public extension Api { public static func parse_messageActionChatCreate(_ reader: BufferReader) -> MessageAction? { var _1: String? _1 = parseString(reader) - var _2: [Int64]? + var _2: [Int32]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil let _c2 = _2 != nil @@ -21086,9 +21253,9 @@ public extension Api { return Api.MessageAction.messageActionChatDeletePhoto } public static func parse_messageActionChatAddUser(_ reader: BufferReader) -> MessageAction? { - var _1: [Int64]? + var _1: [Int32]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil if _c1 { @@ -21099,8 +21266,8 @@ public extension Api { } } public static func parse_messageActionChatDeleteUser(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.MessageAction.messageActionChatDeleteUser(userId: _1!) @@ -21110,8 +21277,8 @@ public extension Api { } } public static func parse_messageActionChatJoinedByLink(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.MessageAction.messageActionChatJoinedByLink(inviterId: _1!) @@ -21132,8 +21299,8 @@ public extension Api { } } public static func parse_messageActionChatMigrateTo(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { return Api.MessageAction.messageActionChatMigrateTo(channelId: _1!) @@ -21145,8 +21312,8 @@ public extension Api { public static func parse_messageActionChannelMigrateFrom(_ reader: BufferReader) -> MessageAction? { var _1: String? _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { @@ -21349,9 +21516,9 @@ public extension Api { if let signature = reader.readInt32() { _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } - var _2: [Int64]? + var _2: [Int32]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil let _c2 = _2 != nil @@ -21389,14 +21556,25 @@ public extension Api { return nil } } + public static func parse_messageActionSetChatTheme(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSetChatTheme(emoticon: _1!) + } + else { + return nil + } + } } public enum PhoneCall: TypeConstructorDescription { case phoneCallEmpty(id: Int64) - case phoneCallWaiting(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, protocol: Api.PhoneCallProtocol, receiveDate: Int32?) - case phoneCallRequested(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAHash: Buffer, protocol: Api.PhoneCallProtocol) - case phoneCallAccepted(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, protocol: Api.PhoneCallProtocol) - case phoneCall(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAOrB: Buffer, keyFingerprint: Int64, protocol: Api.PhoneCallProtocol, connections: [Api.PhoneConnection], startDate: Int32) + case phoneCallWaiting(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int32, participantId: Int32, protocol: Api.PhoneCallProtocol, receiveDate: Int32?) + case phoneCallRequested(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int32, participantId: Int32, gAHash: Buffer, protocol: Api.PhoneCallProtocol) + case phoneCallAccepted(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int32, participantId: Int32, gB: Buffer, protocol: Api.PhoneCallProtocol) + case phoneCall(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int32, participantId: Int32, gAOrB: Buffer, keyFingerprint: Int64, protocol: Api.PhoneCallProtocol, connections: [Api.PhoneConnection], startDate: Int32) case phoneCallDiscarded(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -21409,53 +21587,53 @@ public extension Api { break case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate): if boxed { - buffer.appendInt32(-987599081) + buffer.appendInt32(462375633) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) + serializeInt32(participantId, buffer: buffer, boxed: false) `protocol`.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(receiveDate!, buffer: buffer, boxed: false)} break case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`): if boxed { - buffer.appendInt32(347139340) + buffer.appendInt32(-2014659757) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) + serializeInt32(participantId, buffer: buffer, boxed: false) serializeBytes(gAHash, buffer: buffer, boxed: false) `protocol`.serialize(buffer, true) break case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`): if boxed { - buffer.appendInt32(912311057) + buffer.appendInt32(-1719909046) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) + serializeInt32(participantId, buffer: buffer, boxed: false) serializeBytes(gB, buffer: buffer, boxed: false) `protocol`.serialize(buffer, true) break case .phoneCall(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAOrB, let keyFingerprint, let `protocol`, let connections, let startDate): if boxed { - buffer.appendInt32(-1770029977) + buffer.appendInt32(-2025673089) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) + serializeInt32(participantId, buffer: buffer, boxed: false) serializeBytes(gAOrB, buffer: buffer, boxed: false) serializeInt64(keyFingerprint, buffer: buffer, boxed: false) `protocol`.serialize(buffer, true) @@ -21515,10 +21693,10 @@ public extension Api { _3 = reader.readInt64() var _4: Int32? _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() var _7: Api.PhoneCallProtocol? if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol @@ -21549,10 +21727,10 @@ public extension Api { _3 = reader.readInt64() var _4: Int32? _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() var _7: Buffer? _7 = parseBytes(reader) var _8: Api.PhoneCallProtocol? @@ -21583,10 +21761,10 @@ public extension Api { _3 = reader.readInt64() var _4: Int32? _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() var _7: Buffer? _7 = parseBytes(reader) var _8: Api.PhoneCallProtocol? @@ -21617,10 +21795,10 @@ public extension Api { _3 = reader.readInt64() var _4: Int32? _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() var _7: Buffer? _7 = parseBytes(reader) var _8: Int64? @@ -21828,13 +22006,13 @@ public extension Api { } public enum Theme: TypeConstructorDescription { - case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: Api.ThemeSettings?, installsCount: Int32) + case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: Api.ThemeSettings?, installsCount: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let installsCount): if boxed { - buffer.appendInt32(42930452) + buffer.appendInt32(-402474788) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -21843,7 +22021,7 @@ public extension Api { serializeString(title, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} if Int(flags) & Int(1 << 3) != 0 {settings!.serialize(buffer, true)} - serializeInt32(installsCount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(installsCount!, buffer: buffer, boxed: false)} break } } @@ -21875,7 +22053,7 @@ public extension Api { _7 = Api.parse(reader, signature: signature) as? Api.ThemeSettings } } var _8: Int32? - _8 = reader.readInt32() + if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -21883,9 +22061,9 @@ public extension Api { let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = _8 != nil + let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, installsCount: _8!) + return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, installsCount: _8) } else { return nil @@ -21928,19 +22106,22 @@ public extension Api { } public enum ThemeSettings: TypeConstructorDescription { - case themeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, messageTopColor: Int32?, messageBottomColor: Int32?, wallpaper: Api.WallPaper?) + case themeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, messageColors: [Int32]?, wallpaper: Api.WallPaper?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .themeSettings(let flags, let baseTheme, let accentColor, let messageTopColor, let messageBottomColor, let wallpaper): + case .themeSettings(let flags, let baseTheme, let accentColor, let messageColors, let wallpaper): if boxed { - buffer.appendInt32(-1676371894) + buffer.appendInt32(-1917524116) } serializeInt32(flags, buffer: buffer, boxed: false) baseTheme.serialize(buffer, true) serializeInt32(accentColor, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(messageTopColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(messageBottomColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messageColors!.count)) + for item in messageColors! { + serializeInt32(item, buffer: buffer, boxed: false) + }} if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)} break } @@ -21948,8 +22129,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .themeSettings(let flags, let baseTheme, let accentColor, let messageTopColor, let messageBottomColor, let wallpaper): - return ("themeSettings", [("flags", flags), ("baseTheme", baseTheme), ("accentColor", accentColor), ("messageTopColor", messageTopColor), ("messageBottomColor", messageBottomColor), ("wallpaper", wallpaper)]) + case .themeSettings(let flags, let baseTheme, let accentColor, let messageColors, let wallpaper): + return ("themeSettings", [("flags", flags), ("baseTheme", baseTheme), ("accentColor", accentColor), ("messageColors", messageColors), ("wallpaper", wallpaper)]) } } @@ -21962,22 +22143,21 @@ public extension Api { } var _3: Int32? _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } - var _6: Api.WallPaper? + var _4: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + var _5: Api.WallPaper? if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WallPaper + _5 = Api.parse(reader, signature: signature) as? Api.WallPaper } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, messageTopColor: _4, messageBottomColor: _5, wallpaper: _6) + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, messageColors: _4, wallpaper: _5) } else { return nil @@ -22041,7 +22221,6 @@ public extension Api { } public enum InputBotInlineMessageID: TypeConstructorDescription { case inputBotInlineMessageID(dcId: Int32, id: Int64, accessHash: Int64) - case inputBotInlineMessageID64(dcId: Int32, ownerId: Int64, id: Int32, accessHash: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -22053,15 +22232,6 @@ public extension Api { serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) break - case .inputBotInlineMessageID64(let dcId, let ownerId, let id, let accessHash): - if boxed { - buffer.appendInt32(-1227287081) - } - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeInt64(ownerId, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break } } @@ -22069,8 +22239,6 @@ public extension Api { switch self { case .inputBotInlineMessageID(let dcId, let id, let accessHash): return ("inputBotInlineMessageID", [("dcId", dcId), ("id", id), ("accessHash", accessHash)]) - case .inputBotInlineMessageID64(let dcId, let ownerId, let id, let accessHash): - return ("inputBotInlineMessageID64", [("dcId", dcId), ("ownerId", ownerId), ("id", id), ("accessHash", accessHash)]) } } @@ -22091,26 +22259,6 @@ public extension Api { return nil } } - public static func parse_inputBotInlineMessageID64(_ reader: BufferReader) -> InputBotInlineMessageID? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBotInlineMessageID.inputBotInlineMessageID64(dcId: _1!, ownerId: _2!, id: _3!, accessHash: _4!) - } - else { - return nil - } - } } public enum PageRelatedArticle: TypeConstructorDescription { @@ -22371,7 +22519,7 @@ public extension Api { case messageEntityCode(offset: Int32, length: Int32) case messageEntityPre(offset: Int32, length: Int32, language: String) case messageEntityTextUrl(offset: Int32, length: Int32, url: String) - case messageEntityMentionName(offset: Int32, length: Int32, userId: Int64) + case messageEntityMentionName(offset: Int32, length: Int32, userId: Int32) case inputMessageEntityMentionName(offset: Int32, length: Int32, userId: Api.InputUser) case messageEntityPhone(offset: Int32, length: Int32) case messageEntityCashtag(offset: Int32, length: Int32) @@ -22463,11 +22611,11 @@ public extension Api { break case .messageEntityMentionName(let offset, let length, let userId): if boxed { - buffer.appendInt32(-595914432) + buffer.appendInt32(892193368) } serializeInt32(offset, buffer: buffer, boxed: false) serializeInt32(length, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) break case .inputMessageEntityMentionName(let offset, let length, let userId): if boxed { @@ -22730,8 +22878,8 @@ public extension Api { _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -22973,9 +23121,9 @@ public extension Api { } public enum EncryptedChat: TypeConstructorDescription { case encryptedChatEmpty(id: Int32) - case encryptedChatWaiting(id: Int32, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64) - case encryptedChatRequested(flags: Int32, folderId: Int32?, id: Int32, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gA: Buffer) - case encryptedChat(id: Int32, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAOrB: Buffer, keyFingerprint: Int64) + case encryptedChatWaiting(id: Int32, accessHash: Int64, date: Int32, adminId: Int32, participantId: Int32) + case encryptedChatRequested(flags: Int32, folderId: Int32?, id: Int32, accessHash: Int64, date: Int32, adminId: Int32, participantId: Int32, gA: Buffer) + case encryptedChat(id: Int32, accessHash: Int64, date: Int32, adminId: Int32, participantId: Int32, gAOrB: Buffer, keyFingerprint: Int64) case encryptedChatDiscarded(flags: Int32, id: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -22988,36 +23136,36 @@ public extension Api { break case .encryptedChatWaiting(let id, let accessHash, let date, let adminId, let participantId): if boxed { - buffer.appendInt32(1722964307) + buffer.appendInt32(1006044124) } serializeInt32(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) + serializeInt32(participantId, buffer: buffer, boxed: false) break case .encryptedChatRequested(let flags, let folderId, let id, let accessHash, let date, let adminId, let participantId, let gA): if boxed { - buffer.appendInt32(1223809356) + buffer.appendInt32(1651608194) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} serializeInt32(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) + serializeInt32(participantId, buffer: buffer, boxed: false) serializeBytes(gA, buffer: buffer, boxed: false) break case .encryptedChat(let id, let accessHash, let date, let adminId, let participantId, let gAOrB, let keyFingerprint): if boxed { - buffer.appendInt32(1643173063) + buffer.appendInt32(-94974410) } serializeInt32(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(adminId, buffer: buffer, boxed: false) - serializeInt64(participantId, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) + serializeInt32(participantId, buffer: buffer, boxed: false) serializeBytes(gAOrB, buffer: buffer, boxed: false) serializeInt64(keyFingerprint, buffer: buffer, boxed: false) break @@ -23064,10 +23212,10 @@ public extension Api { _2 = reader.readInt64() var _3: Int32? _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - _5 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -23091,10 +23239,10 @@ public extension Api { _4 = reader.readInt64() var _5: Int32? _5 = reader.readInt32() - var _6: Int64? - _6 = reader.readInt64() - var _7: Int64? - _7 = reader.readInt64() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() var _8: Buffer? _8 = parseBytes(reader) let _c1 = _1 != nil @@ -23119,10 +23267,10 @@ public extension Api { _2 = reader.readInt64() var _3: Int32? _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - _5 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() var _6: Buffer? _6 = parseBytes(reader) var _7: Int64? @@ -23334,16 +23482,16 @@ public extension Api { } public enum WebAuthorization: TypeConstructorDescription { - case webAuthorization(hash: Int64, botId: Int64, domain: String, browser: String, platform: String, dateCreated: Int32, dateActive: Int32, ip: String, region: String) + case webAuthorization(hash: Int64, botId: Int32, domain: String, browser: String, platform: String, dateCreated: Int32, dateActive: Int32, ip: String, region: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .webAuthorization(let hash, let botId, let domain, let browser, let platform, let dateCreated, let dateActive, let ip, let region): if boxed { - buffer.appendInt32(-1493633966) + buffer.appendInt32(-892779534) } serializeInt64(hash, buffer: buffer, boxed: false) - serializeInt64(botId, buffer: buffer, boxed: false) + serializeInt32(botId, buffer: buffer, boxed: false) serializeString(domain, buffer: buffer, boxed: false) serializeString(browser, buffer: buffer, boxed: false) serializeString(platform, buffer: buffer, boxed: false) @@ -23365,8 +23513,8 @@ public extension Api { public static func parse_webAuthorization(_ reader: BufferReader) -> WebAuthorization? { var _1: Int64? _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() var _3: String? _3 = parseString(reader) var _4: String? @@ -23400,15 +23548,15 @@ public extension Api { } public enum ImportedContact: TypeConstructorDescription { - case importedContact(userId: Int64, clientId: Int64) + case importedContact(userId: Int32, clientId: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .importedContact(let userId, let clientId): if boxed { - buffer.appendInt32(-1052885936) + buffer.appendInt32(-805141448) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) serializeInt64(clientId, buffer: buffer, boxed: false) break } @@ -23422,8 +23570,8 @@ public extension Api { } public static func parse_importedContact(_ reader: BufferReader) -> ImportedContact? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() let _c1 = _1 != nil diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index c37cd6abf7..aa66a74db5 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -1271,6 +1271,62 @@ public struct account { } } + } + public enum ChatThemes: TypeConstructorDescription { + case chatThemesNotModified + case chatThemes(hash: Int32, themes: [Api.ChatTheme]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chatThemesNotModified: + if boxed { + buffer.appendInt32(-535699004) + } + + break + case .chatThemes(let hash, let themes): + if boxed { + buffer.appendInt32(-28524867) + } + serializeInt32(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(themes.count)) + for item in themes { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .chatThemesNotModified: + return ("chatThemesNotModified", []) + case .chatThemes(let hash, let themes): + return ("chatThemes", [("hash", hash), ("themes", themes)]) + } + } + + public static func parse_chatThemesNotModified(_ reader: BufferReader) -> ChatThemes? { + return Api.account.ChatThemes.chatThemesNotModified + } + public static func parse_chatThemes(_ reader: BufferReader) -> ChatThemes? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.ChatTheme]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatTheme.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.ChatThemes.chatThemes(hash: _1!, themes: _2!) + } + else { + return nil + } + } + } public enum Authorizations: TypeConstructorDescription { case authorizations(authorizations: [Api.Authorization]) @@ -4376,6 +4432,21 @@ public extension Api { return result }) } + + public static func setChatTheme(peer: Api.InputPeer, emoticon: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-432283329) + peer.serialize(buffer, true) + serializeString(emoticon, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setChatTheme", parameters: [("peer", peer), ("emoticon", emoticon)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -4954,6 +5025,35 @@ public extension Api { return result }) } + + public static func viewSponsoredMessage(channel: Api.InputChannel, randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1095836780) + channel.serialize(buffer, true) + serializeBytes(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.viewSponsoredMessage", parameters: [("channel", channel), ("randomId", randomId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } + + public static func getSponsoredMessages(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-333377601) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.getSponsoredMessages", parameters: [("channel", channel)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SponsoredMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.SponsoredMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SponsoredMessages + } + return result + }) + } } public struct payments { public static func getPaymentForm(flags: Int32, peer: Api.InputPeer, msgId: Int32, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -7616,6 +7716,20 @@ public extension Api { return result }) } + + public static func getChatThemes(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-690545285) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ChatThemes? in + let reader = BufferReader(buffer) + var result: Api.account.ChatThemes? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ChatThemes + } + return result + }) + } } public struct langpack { public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -8070,13 +8184,14 @@ public extension Api { }) } - public static func toggleGroupCallRecord(flags: Int32, call: Api.InputGroupCall, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func toggleGroupCallRecord(flags: Int32, call: Api.InputGroupCall, title: String?, videoPortrait: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1070962985) + buffer.appendInt32(-248985848) serializeInt32(flags, buffer: buffer, boxed: false) call.serialize(buffer, true) if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "phone.toggleGroupCallRecord", parameters: [("flags", flags), ("call", call), ("title", title)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 2) != 0 {videoPortrait!.serialize(buffer, true)} + return (FunctionDescription(name: "phone.toggleGroupCallRecord", parameters: [("flags", flags), ("call", call), ("title", title), ("videoPortrait", videoPortrait)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift index ec1a7572a7..95bd44981b 100644 --- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift @@ -138,7 +138,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode { } private func setupGradientAnimations() { - if let _ = self.foregroundGradientLayer.animation(forKey: "movement") { + /*if let _ = self.foregroundGradientLayer.animation(forKey: "movement") { } else { let previousValue = self.foregroundGradientLayer.startPoint let newValue: CGPoint @@ -162,7 +162,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode { self.foregroundGradientLayer.add(animation, forKey: "movement") CATransaction.commit() - } + }*/ } func updateAnimations() { @@ -172,7 +172,9 @@ private class CallStatusBarBackgroundNode: ASDisplayNode { return } self.setupGradientAnimations() - self.maskCurveView.startAnimating() + if isCurrentlyInHierarchy { + self.maskCurveView.startAnimating() + } } } @@ -206,6 +208,9 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { private var currentScheduleTimestamp: Int32? private var currentMembers: PresentationGroupCallMembers? private var currentIsConnected = true + + private let hierarchyTrackingNode: HierarchyTrackingNode + private var isCurrentlyInHierarchy = true public override init() { self.backgroundNode = CallStatusBarBackgroundNode() @@ -213,13 +218,29 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { self.subtitleNode = ImmediateAnimatedCountLabelNode() self.subtitleNode.reverseAnimationDirection = true self.speakerNode = ImmediateTextNode() + + var updateInHierarchy: ((Bool) -> Void)? + self.hierarchyTrackingNode = HierarchyTrackingNode({ value in + updateInHierarchy?(value) + }) super.init() + + self.addSubnode(self.hierarchyTrackingNode) self.addSubnode(self.backgroundNode) self.addSubnode(self.titleNode) self.addSubnode(self.subtitleNode) self.addSubnode(self.speakerNode) + + updateInHierarchy = { [weak self] value in + if let strongSelf = self { + strongSelf.isCurrentlyInHierarchy = value + if value { + strongSelf.update() + } + } + } } deinit { @@ -231,13 +252,17 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { public func update(content: Content) { self.currentContent = content - self.update() + if self.isCurrentlyInHierarchy { + self.update() + } } public override func update(size: CGSize) { self.currentSize = size self.update() } + + private let textFont = Font.with(size: 13.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]) private func update() { guard let size = self.currentSize, let content = self.currentContent else { @@ -329,8 +354,10 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { currentIsConnected = false } strongSelf.currentIsConnected = currentIsConnected - - strongSelf.update() + + if strongSelf.isCurrentlyInHierarchy { + strongSelf.update() + } } })) self.audioLevelDisposable.set((combineLatest(call.myAudioLevel, .single([]) |> then(call.audioLevels)) @@ -351,8 +378,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { var title: String = "" var speakerSubtitle: String = "" - - let textFont = Font.with(size: 13.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]) + let textColor = UIColor.white var segments: [AnimatedCountLabelNode.Segment] = [] var displaySpeakerSubtitle = false diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index d1dfdfd0c6..96f12b533c 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -49,7 +49,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { private let isMediaPlaying: () -> Bool private let resumeMediaPlayback: () -> Void - private let accountManager: AccountManager + private let accountManager: AccountManager private let audioSession: ManagedAudioSession private let callKitIntegration: CallKitIntegration? @@ -113,7 +113,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { return OngoingCallContext.versions(includeExperimental: includeExperimental, includeReference: includeReference) } - public init(accountManager: AccountManager, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[AccountContext], NoError>) { + public init(accountManager: AccountManager, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[AccountContext], NoError>) { self.getDeviceAccessData = getDeviceAccessData self.accountManager = accountManager self.audioSession = audioSession diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 3499f3dee9..4071824367 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -1382,8 +1382,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } else { var outgoingAudioBitrateKbit: Int32? let appConfiguration = self.accountContext.currentAppConfiguration.with({ $0 }) - if let data = appConfiguration.data, let value = data["voice_chat_send_bitrate"] as? Int32 { - outgoingAudioBitrateKbit = value + if let data = appConfiguration.data, let value = data["voice_chat_send_bitrate"] as? Double { + outgoingAudioBitrateKbit = Int32(value) } genericCallContext = OngoingGroupCallContext(video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in @@ -2859,14 +2859,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } - public func setShouldBeRecording(_ shouldBeRecording: Bool, title: String?) { + public func setShouldBeRecording(_ shouldBeRecording: Bool, title: String?, videoOrientation: Bool?) { if !self.stateValue.canManageCall { return } if (self.stateValue.recordingStartTimestamp != nil) == shouldBeRecording { return } - self.participantsContext?.updateShouldBeRecording(shouldBeRecording, title: title) + self.participantsContext?.updateShouldBeRecording(shouldBeRecording, title: title, videoOrientation: videoOrientation) } private func requestCall(movingFromBroadcastToRtc: Bool) { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift index 2b6b756bbf..b6b3160275 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift @@ -1249,6 +1249,9 @@ private final class VoiceBlobView: UIView { private(set) var isAnimating = false public typealias BlobRange = (min: CGFloat, max: CGFloat) + + private let hierarchyTrackingNode: HierarchyTrackingNode + private var isCurrentlyInHierarchy = true public init( frame: CGRect, @@ -1256,6 +1259,11 @@ private final class VoiceBlobView: UIView { mediumBlobRange: BlobRange, bigBlobRange: BlobRange ) { + var updateInHierarchy: ((Bool) -> Void)? + self.hierarchyTrackingNode = HierarchyTrackingNode({ value in + updateInHierarchy?(value) + }) + self.maxLevel = maxLevel self.mediumBlob = BlobView( @@ -1278,18 +1286,30 @@ private final class VoiceBlobView: UIView { ) super.init(frame: frame) + + addSubnode(hierarchyTrackingNode) addSubview(bigBlob) addSubview(mediumBlob) displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in guard let strongSelf = self else { return } + + if !strongSelf.isCurrentlyInHierarchy { + return + } strongSelf.presentationAudioLevel = strongSelf.presentationAudioLevel * 0.9 + strongSelf.audioLevel * 0.1 strongSelf.mediumBlob.level = strongSelf.presentationAudioLevel strongSelf.bigBlob.level = strongSelf.presentationAudioLevel } + + updateInHierarchy = { [weak self] value in + if let strongSelf = self { + strongSelf.isCurrentlyInHierarchy = value + } + } } required init?(coder: NSCoder) { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index a2578444b8..101d082160 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1119,7 +1119,7 @@ public final class VoiceChatController: ViewController { self.scheduleTextNode.textAlignment = .center self.scheduleTextNode.maximumNumberOfLines = 4 - self.scheduleCancelButton = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x2b2b2f), foregroundColor: .white), height: 52.0, cornerRadius: 10.0) + self.scheduleCancelButton = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x2b2b2f), foregroundColor: .white), height: 52.0, cornerRadius: 10.0) self.scheduleCancelButton.isHidden = !self.isScheduling self.dateFormatter = DateFormatter() @@ -2410,7 +2410,6 @@ public final class VoiceChatController: ViewController { return [] } - let presentationData = strongSelf.presentationData var items: [ContextMenuItem] = [] if peers.count > 1 { @@ -2552,7 +2551,7 @@ public final class VoiceChatController: ViewController { let alertController = textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: strongSelf.presentationData.strings.VoiceChat_StopRecordingTitle, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_StopRecordingStop, action: { if let strongSelf = self { - strongSelf.call.setShouldBeRecording(false, title: nil) + strongSelf.call.setShouldBeRecording(false, title: nil, videoOrientation: nil) strongSelf.presentUndoOverlay(content: .forward(savedMessages: true, text: strongSelf.presentationData.strings.VoiceChat_RecordingSaved), action: { [weak self] value in if case .info = value, let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { @@ -2582,14 +2581,22 @@ public final class VoiceChatController: ViewController { return } - let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_StartRecordingTitle, text: presentationData.strings.VoiceChat_StartRecordingText, placeholder: presentationData.strings.VoiceChat_RecordingTitlePlaceholder, value: nil, maxLength: 40, apply: { title in - if let strongSelf = self, let title = title { - strongSelf.call.setShouldBeRecording(true, title: title) + let controller = VoiceChatRecordingSetupController(context: strongSelf.context, completion: { [weak self] videoOrientation in + if let strongSelf = self { + strongSelf.call.setShouldBeRecording(true, title: "", videoOrientation: videoOrientation) strongSelf.presentUndoOverlay(content: .voiceChatRecording(text: strongSelf.presentationData.strings.VoiceChat_RecordingStarted), action: { _ in return false }) strongSelf.call.playTone(.recordingStarted) } }) +// let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_StartRecordingTitle, text: presentationData.strings.VoiceChat_StartRecordingText, placeholder: presentationData.strings.VoiceChat_RecordingTitlePlaceholder, value: nil, maxLength: 40, apply: { title in +// if let strongSelf = self, let title = title { +// strongSelf.call.setShouldBeRecording(true, title: title) +// +// strongSelf.presentUndoOverlay(content: .voiceChatRecording(text: strongSelf.presentationData.strings.VoiceChat_RecordingStarted), action: { _ in return false }) +// strongSelf.call.playTone(.recordingStarted) +// } +// }) self?.controller?.present(controller, in: .window(.root)) }))) } @@ -4884,6 +4891,8 @@ public final class VoiceChatController: ViewController { memberPeer = user.withUpdatedPhoto([photo]) } } + + joinedVideo = true if let videoEndpointId = member.videoEndpointId { peerIdToCameraEndpointId[member.peer.id] = videoEndpointId @@ -6151,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/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index 209ef94c91..d2b25b7365 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -202,6 +202,16 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { var item: VoiceChatFullscreenParticipantItem? { return self.layoutParams?.0 } + + private var isCurrentlyInHierarchy = false { + didSet { + if self.isCurrentlyInHierarchy != oldValue { + self.highlightNode.isCurrentlyInHierarchy = self.isCurrentlyInHierarchy + self.audioLevelView?.isManuallyInHierarchy = self.isCurrentlyInHierarchy + } + } + } + private var isCurrentlyInHierarchyDisposable: Disposable? init() { self.contextSourceNode = ContextExtractedContentContainingNode() @@ -247,7 +257,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.actionContainerNode = ASDisplayNode() self.actionButtonNode = HighlightableButtonNode() - + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true @@ -293,6 +303,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.audioLevelDisposable.dispose() self.raiseHandTimer?.invalidate() self.silenceTimer?.invalidate() + self.isCurrentlyInHierarchyDisposable?.dispose() } override func selected() { @@ -971,6 +982,16 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.actionButtonNode, frame: animationFrame) strongSelf.updateIsHighlighted(transition: transition) + + if strongSelf.isCurrentlyInHierarchyDisposable == nil { + strongSelf.isCurrentlyInHierarchyDisposable = (item.context.sharedContext.applicationBindings.applicationInForeground + |> deliverOnMainQueue).start(next: { value in + guard let strongSelf = self else { + return + } + strongSelf.isCurrentlyInHierarchy = value + }) + } } }) } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift new file mode 100644 index 0000000000..98eb6e0397 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift @@ -0,0 +1,609 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import SolidRoundedButtonNode +import PresentationDataUtils + +private let accentColor: UIColor = UIColor(rgb: 0x007aff) + +final class VoiceChatRecordingSetupController: ViewController { + private var controllerNode: VoiceChatRecordingSetupControllerNode { + return self.displayNode as! VoiceChatRecordingSetupControllerNode + } + + private let context: AccountContext + private let completion: (Bool?) -> Void + + private var animatedIn = false + + private var presentationDataDisposable: Disposable? + + init(context: AccountContext, completion: @escaping (Bool?) -> Void) { + self.context = context + self.completion = completion + + super.init(navigationBarPresentationData: nil) + + self.statusBar.statusBarStyle = .Ignore + + self.blocksBackgroundWhenInOverlay = true + + self.presentationDataDisposable = (context.sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + 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 = VoiceChatRecordingSetupControllerNode(controller: self, context: self.context) + self.controllerNode.completion = { [weak self] videoOrientation in + self?.completion(videoOrientation) + } + self.controllerNode.dismiss = { [weak self] in + self?.presentingViewController?.dismiss(animated: false, completion: nil) + } + self.controllerNode.cancel = { [weak self] in + 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.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) + } +} + +private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { + enum MediaMode { + case videoAndAudio + case audioOnly + } + + enum VideoMode { + case portrait + case landscape + } + + private weak var controller: VoiceChatRecordingSetupController? + private let context: AccountContext + private var presentationData: PresentationData + + private let dimNode: ASDisplayNode + private let wrappingScrollNode: ASScrollNode + private let contentContainerNode: ASDisplayNode + private let effectNode: ASDisplayNode + private let backgroundNode: ASDisplayNode + private let contentBackgroundNode: ASDisplayNode + private let titleNode: ASTextNode + private let doneButton: VoiceChatActionButton + private let cancelButton: SolidRoundedButtonNode + private let modeContainerNode: ASDisplayNode + + private let modeSeparatorNode: ASDisplayNode + private let videoAudioButton: HighlightTrackingButtonNode + private let videoAudioTitleNode: ImmediateTextNode + private let videoAudioCheckNode: ASImageNode + + private let audioButton: HighlightTrackingButtonNode + private let audioTitleNode: ImmediateTextNode + private let audioCheckNode: ASImageNode + + private let portraitButton: HighlightTrackingButtonNode + private let portraitIconNode: PreviewIconNode + private let portraitTitleNode: ImmediateTextNode + + private let landscapeButton: HighlightTrackingButtonNode + private let landscapeIconNode: PreviewIconNode + private let landscapeTitleNode: ImmediateTextNode + + private let selectionNode: ASImageNode + + private var containerLayout: (ContainerViewLayout, CGFloat)? + + private let hapticFeedback = HapticFeedback() + + private let readyDisposable = MetaDisposable() + + private var mediaMode: MediaMode = .videoAndAudio + private var videoMode: VideoMode = .portrait + + var completion: ((Bool?) -> Void)? + var dismiss: (() -> Void)? + var cancel: (() -> Void)? + + init(controller: VoiceChatRecordingSetupController, context: AccountContext) { + self.controller = controller + self.context = context + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + + self.wrappingScrollNode = ASScrollNode() + self.wrappingScrollNode.view.alwaysBounceVertical = true + self.wrappingScrollNode.view.delaysContentTouches = false + self.wrappingScrollNode.view.canCancelContentTouches = true + + self.dimNode = ASDisplayNode() + self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + + self.contentContainerNode = ASDisplayNode() + self.contentContainerNode.isOpaque = false + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.clipsToBounds = true + self.backgroundNode.cornerRadius = 16.0 + + let backgroundColor = UIColor(rgb: 0x1c1c1e) + let textColor: UIColor = .white + let buttonColor: UIColor = UIColor(rgb: 0x2b2b2f) + let buttonTextColor: UIColor = .white + let blurStyle: UIBlurEffect.Style = .dark + + self.effectNode = ASDisplayNode(viewBlock: { + return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) + }) + + self.contentBackgroundNode = ASDisplayNode() + self.contentBackgroundNode.backgroundColor = backgroundColor + + let title = "Record Voice Chat" + + self.titleNode = ASTextNode() + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor) + + self.doneButton = VoiceChatActionButton() + + self.cancelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false) + self.cancelButton.title = self.presentationData.strings.Common_Cancel + + self.modeContainerNode = ASDisplayNode() + self.modeContainerNode.clipsToBounds = true + self.modeContainerNode.cornerRadius = 11.0 + self.modeContainerNode.backgroundColor = UIColor(rgb: 0x303032) + + self.modeSeparatorNode = ASDisplayNode() + self.modeSeparatorNode.backgroundColor = UIColor(rgb: 0x404041) + + self.videoAudioButton = HighlightTrackingButtonNode() + self.videoAudioTitleNode = ImmediateTextNode() + self.videoAudioTitleNode.attributedText = NSAttributedString(string: "Video and Audio", font: Font.regular(17.0), textColor: .white, paragraphAlignment: .left) + self.videoAudioCheckNode = ASImageNode() + self.videoAudioCheckNode.displaysAsynchronously = false + self.videoAudioCheckNode.image = UIImage(bundleImageName: "Call/Check") + + self.audioButton = HighlightTrackingButtonNode() + self.audioTitleNode = ImmediateTextNode() + self.audioTitleNode.attributedText = NSAttributedString(string: "Only Audio", font: Font.regular(17.0), textColor: .white, paragraphAlignment: .left) + self.audioCheckNode = ASImageNode() + self.audioCheckNode.displaysAsynchronously = false + self.audioCheckNode.image = UIImage(bundleImageName: "Call/Check") + + self.portraitButton = HighlightTrackingButtonNode() + self.portraitButton.backgroundColor = UIColor(rgb: 0x303032) + self.portraitButton.cornerRadius = 11.0 + self.portraitIconNode = PreviewIconNode() + self.portraitTitleNode = ImmediateTextNode() + self.portraitTitleNode.attributedText = NSAttributedString(string: "Portrait", font: Font.semibold(15.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .left) + + self.landscapeButton = HighlightTrackingButtonNode() + self.landscapeButton.backgroundColor = UIColor(rgb: 0x303032) + self.landscapeButton.cornerRadius = 11.0 + self.landscapeIconNode = PreviewIconNode() + self.landscapeTitleNode = ImmediateTextNode() + self.landscapeTitleNode.attributedText = NSAttributedString(string: "Landscape", font: Font.semibold(15.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .left) + + self.selectionNode = ASImageNode() + self.selectionNode.displaysAsynchronously = false + self.selectionNode.image = generateImage(CGSize(width: 174.0, height: 140.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let lineWidth: CGFloat = 2.0 + + let path = UIBezierPath(roundedRect: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), cornerRadius: 11.0) + let cgPath = path.cgPath.copy(strokingWithWidth: lineWidth, lineCap: .round, lineJoin: .round, miterLimit: 10.0) + context.addPath(cgPath) + context.clip() + + let colors: [CGColor] = [UIColor(rgb: 0x5064fd).cgColor, UIColor(rgb: 0xe76598).cgColor] + var locations: [CGFloat] = [0.0, 1.0] + let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colors as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) + }) + self.selectionNode.isUserInteractionEnabled = false + + 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.backgroundNode.addSubnode(self.effectNode) + self.backgroundNode.addSubnode(self.contentBackgroundNode) + self.contentContainerNode.addSubnode(self.titleNode) + self.contentContainerNode.addSubnode(self.doneButton) + self.contentContainerNode.addSubnode(self.cancelButton) + self.contentContainerNode.addSubnode(self.modeContainerNode) + + self.contentContainerNode.addSubnode(self.videoAudioTitleNode) + self.contentContainerNode.addSubnode(self.videoAudioCheckNode) + self.contentContainerNode.addSubnode(self.videoAudioButton) + + self.contentContainerNode.addSubnode(self.modeSeparatorNode) + + self.contentContainerNode.addSubnode(self.audioTitleNode) + self.contentContainerNode.addSubnode(self.audioCheckNode) + self.contentContainerNode.addSubnode(self.audioButton) + + self.contentContainerNode.addSubnode(self.portraitButton) + self.contentContainerNode.addSubnode(self.portraitIconNode) + self.contentContainerNode.addSubnode(self.portraitTitleNode) + + self.contentContainerNode.addSubnode(self.landscapeButton) + self.contentContainerNode.addSubnode(self.landscapeIconNode) + self.contentContainerNode.addSubnode(self.landscapeTitleNode) + + self.contentContainerNode.addSubnode(self.selectionNode) + + self.videoAudioButton.addTarget(self, action: #selector(self.videoAudioPressed), forControlEvents: .touchUpInside) + self.audioButton.addTarget(self, action: #selector(self.audioPressed), forControlEvents: .touchUpInside) + + self.portraitButton.addTarget(self, action: #selector(self.portraitPressed), forControlEvents: .touchUpInside) + self.landscapeButton.addTarget(self, action: #selector(self.landscapePressed), forControlEvents: .touchUpInside) + + self.doneButton.addTarget(self, action: #selector(self.donePressed), forControlEvents: .touchUpInside) + + self.cancelButton.pressed = { [weak self] in + if let strongSelf = self { + strongSelf.cancel?() + } + } + } + + @objc private func donePressed() { + let videoOrientation: Bool? + switch self.mediaMode { + case .audioOnly: + videoOrientation = nil + case .videoAndAudio: + switch self.videoMode { + case .portrait: + videoOrientation = true + case .landscape: + videoOrientation = false + } + } + self.completion?(videoOrientation) + self.dismiss?() + } + + @objc private func videoAudioPressed() { + self.mediaMode = .videoAndAudio + + if let (layout, navigationHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + + @objc private func audioPressed() { + self.mediaMode = .audioOnly + + if let (layout, navigationHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + + @objc private func portraitPressed() { + self.mediaMode = .videoAndAudio + self.videoMode = .portrait + + if let (layout, navigationHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + + @objc private func landscapePressed() { + self.mediaMode = .videoAndAudio + self.videoMode = .landscape + + if let (layout, navigationHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + + func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData + } + + override func didLoad() { + super.didLoad() + + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never + } + } + + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.cancel?() + } + } + + 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 + }) + } + + func animateOut(completion: (() -> Void)? = nil) { + 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.cancel?() + } + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.containerLayout = (layout, navigationBarHeight) + + let isLandscape: Bool + if layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass { + isLandscape = true + } else { + isLandscape = false + } + + + var insets = layout.insets(options: [.statusBar, .input]) + let cleanInsets = layout.insets(options: [.statusBar]) + insets.top = max(10.0, insets.top) + + let buttonOffset: CGFloat = 60.0 + + let bottomInset: CGFloat = 10.0 + cleanInsets.bottom + let titleHeight: CGFloat = 54.0 + var contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + let innerContentHeight: CGFloat = 287.0 + var width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) + if isLandscape { + contentHeight = layout.size.height + width = layout.size.width + } else { + contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + innerContentHeight + buttonOffset + } + + let inset: CGFloat = 16.0 + 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.updateAlpha(node: self.titleNode, alpha: isLandscape ? 0.0 : 1.0) + 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 itemHeight: CGFloat = 44.0 + + transition.updateFrame(node: self.modeContainerNode, frame: CGRect(x: inset, y: 56.0, width: contentFrame.width - inset * 2.0, height: itemHeight * 2.0)) + + transition.updateFrame(node: self.videoAudioButton, frame: CGRect(x: inset, y: 56.0, width: contentFrame.width - inset * 2.0, height: itemHeight)) + transition.updateFrame(node: self.videoAudioCheckNode, frame: CGRect(x: contentFrame.width - inset - 16.0 - 20.0, y: 56.0 + floorToScreenPixels((itemHeight - 16.0) / 2.0), width: 16.0, height: 16.0)) + self.videoAudioCheckNode.isHidden = self.mediaMode != .videoAndAudio + + let videoAudioSize = self.videoAudioTitleNode.updateLayout(CGSize(width: contentFrame.width - inset * 2.0, height: itemHeight)) + transition.updateFrame(node: self.videoAudioTitleNode, frame: CGRect(x: inset + 16.0, y: 56.0 + floorToScreenPixels((itemHeight - videoAudioSize.height) / 2.0), width: videoAudioSize.width, height: videoAudioSize.height)) + + transition.updateFrame(node: self.audioButton, frame: CGRect(x: inset, y: 56.0 + itemHeight, width: contentFrame.width - inset * 2.0, height: itemHeight)) + transition.updateFrame(node: self.audioCheckNode, frame: CGRect(x: contentFrame.width - inset - 16.0 - 20.0, y: 56.0 + itemHeight + floorToScreenPixels((itemHeight - 16.0) / 2.0), width: 16.0, height: 16.0)) + self.audioCheckNode.isHidden = self.mediaMode != .audioOnly + + let audioSize = self.audioTitleNode.updateLayout(CGSize(width: contentFrame.width - inset * 2.0, height: itemHeight)) + transition.updateFrame(node: self.audioTitleNode, frame: CGRect(x: inset + 16.0, y: 56.0 + itemHeight + floorToScreenPixels((itemHeight - audioSize.height) / 2.0), width: audioSize.width, height: audioSize.height)) + + transition.updateFrame(node: self.modeSeparatorNode, frame: CGRect(x: inset + 16.0, y: 56.0 + itemHeight, width: contentFrame.width - inset * 2.0 - 16.0, height: UIScreenPixel)) + + var buttonsAlpha: CGFloat = 1.0 + if case .audioOnly = self.mediaMode { + buttonsAlpha = 0.3 + } + + transition.updateAlpha(node: self.portraitButton, alpha: buttonsAlpha) + transition.updateAlpha(node: self.portraitIconNode, alpha: buttonsAlpha) + transition.updateAlpha(node: self.portraitTitleNode, alpha: buttonsAlpha) + + transition.updateAlpha(node: self.landscapeButton, alpha: buttonsAlpha) + transition.updateAlpha(node: self.landscapeIconNode, alpha: buttonsAlpha) + transition.updateAlpha(node: self.landscapeTitleNode, alpha: buttonsAlpha) + + transition.updateAlpha(node: self.selectionNode, alpha: buttonsAlpha) + + self.portraitTitleNode.attributedText = NSAttributedString(string: "Portrait", font: Font.semibold(15.0), textColor: self.videoMode == .portrait ? UIColor(rgb: 0xb56df4) : UIColor(rgb: 0x8e8e93), paragraphAlignment: .left) + self.landscapeTitleNode.attributedText = NSAttributedString(string: "Landscape", font: Font.semibold(15.0), textColor: self.videoMode == .landscape ? UIColor(rgb: 0xb56df4) : UIColor(rgb: 0x8e8e93), paragraphAlignment: .left) + + let buttonWidth = floorToScreenPixels((contentFrame.width - inset * 2.0 - 11.0) / 2.0) + let portraitButtonFrame = CGRect(x: inset, y: 56.0 + itemHeight * 2.0 + 25.0, width: buttonWidth, height: 140.0) + transition.updateFrame(node: self.portraitButton, frame: portraitButtonFrame) + transition.updateFrame(node: self.portraitIconNode, frame: CGRect(x: portraitButtonFrame.minX + floorToScreenPixels((portraitButtonFrame.width - 72.0) / 2.0), y: portraitButtonFrame.minY + floorToScreenPixels((portraitButtonFrame.height - 122.0) / 2.0), width: 76.0, height: 122.0)) + self.portraitIconNode.updateLayout(landscape: false) + let portraitSize = self.portraitTitleNode.updateLayout(CGSize(width: buttonWidth, height: 30.0)) + transition.updateFrame(node: self.portraitTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(portraitButtonFrame.center.x - portraitSize.width / 2.0), y: portraitButtonFrame.maxY + 7.0), size: portraitSize)) + + let landscapeButtonFrame = CGRect(x: portraitButtonFrame.maxX + 11.0, y: portraitButtonFrame.minY, width: portraitButtonFrame.width, height: portraitButtonFrame.height) + transition.updateFrame(node: self.landscapeButton, frame: landscapeButtonFrame) + transition.updateFrame(node: self.landscapeIconNode, frame: CGRect(x: landscapeButtonFrame.minX + floorToScreenPixels((landscapeButtonFrame.width - 122.0) / 2.0), y: landscapeButtonFrame.minY + floorToScreenPixels((landscapeButtonFrame.height - 76.0) / 2.0), width: 122.0, height: 76.0)) + self.landscapeIconNode.updateLayout(landscape: true) + let landscapeSize = self.landscapeTitleNode.updateLayout(CGSize(width: buttonWidth, height: 30.0)) + transition.updateFrame(node: self.landscapeTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(landscapeButtonFrame.center.x - landscapeSize.width / 2.0), y: landscapeButtonFrame.maxY + 7.0), size: landscapeSize)) + + let centralButtonSide = min(contentFrame.width, layout.size.height) - 32.0 + let centralButtonSize = CGSize(width: centralButtonSide, height: centralButtonSide) + + let buttonInset: CGFloat = 16.0 + let doneButtonPreFrame = CGRect(x: buttonInset, y: contentHeight - 50.0 - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width - buttonInset * 2.0, height: 50.0) + let doneButtonFrame = CGRect(origin: CGPoint(x: floor(doneButtonPreFrame.midX - centralButtonSize.width / 2.0), y: floor(doneButtonPreFrame.midY - centralButtonSize.height / 2.0)), size: centralButtonSize) + transition.updateFrame(node: self.doneButton, frame: doneButtonFrame) + + if self.videoMode == .portrait { + self.selectionNode.frame = portraitButtonFrame.insetBy(dx: -1.0, dy: -1.0) + } else { + self.selectionNode.frame = landscapeButtonFrame.insetBy(dx: -1.0, dy: -1.0) + } + + self.doneButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: .button(text: "Start Recording"), title: "", subtitle: "", dark: false, small: false) + + let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) + transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: cancelButtonHeight)) + + transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) + } +} + +private class PreviewIconNode: ASDisplayNode { + private let avatar1Node: ASImageNode + private let avatar2Node: ASImageNode + private let avatar3Node: ASImageNode + private let avatar4Node: ASImageNode + + override init() { + self.avatar1Node = ASImageNode() + self.avatar1Node.cornerRadius = 4.0 + self.avatar1Node.displaysAsynchronously = false + self.avatar1Node.backgroundColor = UIColor(rgb: 0x834fff) + + self.avatar2Node = ASImageNode() + self.avatar2Node.cornerRadius = 4.0 + self.avatar2Node.displaysAsynchronously = false + self.avatar2Node.backgroundColor = UIColor(rgb: 0x63d5c9) + + self.avatar3Node = ASImageNode() + self.avatar3Node.cornerRadius = 4.0 + self.avatar3Node.displaysAsynchronously = false + self.avatar3Node.backgroundColor = UIColor(rgb: 0xccff60) + + self.avatar4Node = ASImageNode() + self.avatar4Node.cornerRadius = 4.0 + self.avatar4Node.displaysAsynchronously = false + self.avatar4Node.backgroundColor = UIColor(rgb: 0xf5512a) + + super.init() + + self.isUserInteractionEnabled = false + + self.addSubnode(self.avatar1Node) + self.addSubnode(self.avatar2Node) + self.addSubnode(self.avatar3Node) + self.addSubnode(self.avatar4Node) + } + + func updateLayout(landscape: Bool) { + if landscape { + self.avatar1Node.frame = CGRect(x: 0.0, y: 0.0, width: 96.0, height: 76.0) + self.avatar2Node.frame = CGRect(x: 98.0, y: 0.0, width: 24.0, height: 24.0) + self.avatar3Node.frame = CGRect(x: 98.0, y: 26.0, width: 24.0, height: 24.0) + self.avatar4Node.frame = CGRect(x: 98.0, y: 52.0, width: 24.0, height: 24.0) + } else { + self.avatar1Node.frame = CGRect(x: 0.0, y: 0.0, width: 76.0, height: 96.0) + self.avatar2Node.frame = CGRect(x: 0.0, y: 98.0, width: 24.0, height: 24.0) + self.avatar3Node.frame = CGRect(x: 26.0, y: 98.0, width: 24.0, height: 24.0) + self.avatar4Node.frame = CGRect(x: 52.0, y: 98.0, width: 24.0, height: 24.0) + } + } +} diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift index fe02c08b24..1c926dc39f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift @@ -157,6 +157,9 @@ final class VoiceChatTileItemNode: ASDisplayNode { private var isExtracted = false private let audioLevelDisposable = MetaDisposable() + + private let hierarchyTrackingNode: HierarchyTrackingNode + private var isCurrentlyInHierarchy = false init(context: AccountContext) { self.context = context @@ -201,7 +204,14 @@ final class VoiceChatTileItemNode: ASDisplayNode { self.placeholderIconNode.contentMode = .scaleAspectFit self.placeholderIconNode.displaysAsynchronously = false + var updateInHierarchy: ((Bool) -> Void)? + self.hierarchyTrackingNode = HierarchyTrackingNode({ value in + updateInHierarchy?(value) + }) + super.init() + + self.addSubnode(self.hierarchyTrackingNode) self.containerNode.addSubnode(self.contextSourceNode) self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode @@ -236,6 +246,13 @@ final class VoiceChatTileItemNode: ASDisplayNode { } strongSelf.updateIsExtracted(isExtracted, transition: transition) } + + updateInHierarchy = { [weak self] value in + if let strongSelf = self { + strongSelf.isCurrentlyInHierarchy = value + strongSelf.highlightNode.isCurrentlyInHierarchy = value + } + } } deinit { @@ -634,9 +651,14 @@ class VoiceChatTileHighlightNode: ASDisplayNode { private let maskLayer = CALayer() private let foregroundGradientLayer = CAGradientLayer() - - private let hierarchyTrackingNode: HierarchyTrackingNode - private var isCurrentlyInHierarchy = false + + var isCurrentlyInHierarchy = false { + didSet { + if self.isCurrentlyInHierarchy != oldValue && self.isCurrentlyInHierarchy { + self.updateAnimations() + } + } + } private var audioLevel: CGFloat = 0.0 private var presentationAudioLevel: CGFloat = 0.0 @@ -650,27 +672,13 @@ class VoiceChatTileHighlightNode: ASDisplayNode { self.foregroundGradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0) self.foregroundGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) - var updateInHierarchy: ((Bool) -> Void)? - self.hierarchyTrackingNode = HierarchyTrackingNode({ value in - updateInHierarchy?(value) - }) - super.init() - updateInHierarchy = { [weak self] value in - if let strongSelf = self { - strongSelf.isCurrentlyInHierarchy = value - strongSelf.updateAnimations() - } - } - self.displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in guard let strongSelf = self else { return } strongSelf.presentationAudioLevel = strongSelf.presentationAudioLevel * 0.9 + strongSelf.audioLevel * 0.1 } - - self.addSubnode(self.hierarchyTrackingNode) } override func didLoad() { @@ -733,8 +741,11 @@ class VoiceChatTileHighlightNode: ASDisplayNode { animation.toValue = newValue CATransaction.setCompletionBlock { [weak self] in - if let isCurrentlyInHierarchy = self?.isCurrentlyInHierarchy, isCurrentlyInHierarchy { - self?.setupGradientAnimations() + guard let strongSelf = self else { + return + } + if strongSelf.isCurrentlyInHierarchy { + strongSelf.setupGradientAnimations() } } diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 11556da21f..f514da28e5 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -122,7 +122,7 @@ public class UnauthorizedAccount { self.stateManager.reset() } - public func changedMasterDatacenterId(accountManager: AccountManager, masterDatacenterId: Int32) -> Signal { + public func changedMasterDatacenterId(accountManager: AccountManager, masterDatacenterId: Int32) -> Signal { if masterDatacenterId == Int32(self.network.mtProto.datacenterId) { return .single(self) } else { @@ -237,7 +237,7 @@ public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecord } } -public func accountWithId(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, backupData: AccountBackupData?, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal { +public func accountWithId(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, backupData: AccountBackupData?, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: false, isReadOnly: false, useCopy: false) @@ -260,7 +260,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw let backupState = AuthorizedAccountState(isTestingEnvironment: beginWithTestingEnvironment, masterDatacenterId: backupData.masterDatacenterId, peerId: PeerId(backupData.peerId), state: nil) state = backupState let dict = NSMutableDictionary() - dict.setObject(MTDatacenterAuthInfo(authKey: backupData.masterDatacenterKey, authKeyId: backupData.masterDatacenterKeyId, saltSet: [], authKeyAttributes: [:]), forKey: backupData.masterDatacenterId as NSNumber) + dict.setObject(MTDatacenterAuthInfo(authKey: backupData.masterDatacenterKey, authKeyId: backupData.masterDatacenterKeyId, saltSet: [], authKeyAttributes: [:])!, forKey: backupData.masterDatacenterId as NSNumber) let data = NSKeyedArchiver.archivedData(withRootObject: dict) transaction.setState(backupState) transaction.setKeychainEntry(data, forKey: "persistent:datacenterAuthInfoById") @@ -904,7 +904,7 @@ public class Account { private var lastSmallLogPostTimestamp: Double? private let smallLogPostDisposable = MetaDisposable() - public init(accountManager: AccountManager, id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, supplementary: Bool) { + public init(accountManager: AccountManager, id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, supplementary: Bool) { self.id = id self.basePath = basePath self.testingEnvironment = testingEnvironment diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index ceb438a8bb..aca987a8e3 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -4,14 +4,77 @@ import SwiftSignalKit import TelegramApi import MtProtoKit - private enum AccountKind { case authorized case unauthorized } +public enum TelegramAccountRecordAttribute: AccountRecordAttribute, Equatable { + enum CodingKeys: String, CodingKey { + case backupData + case environment + case sortOrder + case loggedOut + case legacyRootObject = "_" + } + + case backupData(AccountBackupDataAttribute) + case environment(AccountEnvironmentAttribute) + case sortOrder(AccountSortOrderAttribute) + case loggedOut(LoggedOutAccountAttribute) + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + if let backupData = try? container.decodeIfPresent(AccountBackupDataAttribute.self, forKey: .backupData) { + self = .backupData(backupData) + } else if let environment = try? container.decodeIfPresent(AccountEnvironmentAttribute.self, forKey: .environment) { + self = .environment(environment) + } else if let sortOrder = try? container.decodeIfPresent(AccountSortOrderAttribute.self, forKey: .sortOrder) { + self = .sortOrder(sortOrder) + } else if let loggedOut = try? container.decodeIfPresent(LoggedOutAccountAttribute.self, forKey: .loggedOut) { + self = .loggedOut(loggedOut) + } else { + let legacyRootObjectData = try! container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .legacyRootObject) + if legacyRootObjectData.typeHash == postboxEncodableTypeHash(AccountBackupDataAttribute.self) { + self = .backupData(try! AdaptedPostboxDecoder().decode(AccountBackupDataAttribute.self, from: legacyRootObjectData.data)) + } else if legacyRootObjectData.typeHash == postboxEncodableTypeHash(AccountEnvironmentAttribute.self) { + self = .environment(try! AdaptedPostboxDecoder().decode(AccountEnvironmentAttribute.self, from: legacyRootObjectData.data)) + } else if legacyRootObjectData.typeHash == postboxEncodableTypeHash(AccountSortOrderAttribute.self) { + self = .sortOrder(try! AdaptedPostboxDecoder().decode(AccountSortOrderAttribute.self, from: legacyRootObjectData.data)) + } else if legacyRootObjectData.typeHash == postboxEncodableTypeHash(LoggedOutAccountAttribute.self) { + self = .loggedOut(try! AdaptedPostboxDecoder().decode(LoggedOutAccountAttribute.self, from: legacyRootObjectData.data)) + } else { + preconditionFailure() + } + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .backupData(backupData): + try container.encode(backupData, forKey: .backupData) + case let .environment(environment): + try container.encode(environment, forKey: .environment) + case let .sortOrder(sortOrder): + try container.encode(sortOrder, forKey: .sortOrder) + case let .loggedOut(loggedOut): + try container.encode(loggedOut, forKey: .loggedOut) + } + } + + public func isEqual(to: AccountRecordAttribute) -> Bool { + return self == to as? TelegramAccountRecordAttribute + } +} + +public final class TelegramAccountManagerTypes: AccountManagerTypes { + public typealias Attribute = TelegramAccountRecordAttribute +} + private var declaredEncodables: Void = { - declareEncodable(AuthAccountRecord.self, f: { AuthAccountRecord(decoder: $0) }) declareEncodable(UnauthorizedAccountState.self, f: { UnauthorizedAccountState(decoder: $0) }) declareEncodable(AuthorizedAccountState.self, f: { AuthorizedAccountState(decoder: $0) }) declareEncodable(TelegramUser.self, f: { TelegramUser(decoder: $0) }) @@ -74,9 +137,6 @@ private var declaredEncodables: Void = { declareEncodable(RecentMediaItem.self, f: { RecentMediaItem(decoder: $0) }) declareEncodable(RecentPeerItem.self, f: { RecentPeerItem(decoder: $0) }) declareEncodable(RecentHashtagItem.self, f: { RecentHashtagItem(decoder: $0) }) - declareEncodable(LoggedOutAccountAttribute.self, f: { LoggedOutAccountAttribute(decoder: $0) }) - declareEncodable(AccountEnvironmentAttribute.self, f: { AccountEnvironmentAttribute(decoder: $0) }) - declareEncodable(AccountSortOrderAttribute.self, f: { AccountSortOrderAttribute(decoder: $0) }) declareEncodable(CloudChatClearHistoryOperation.self, f: { CloudChatClearHistoryOperation(decoder: $0) }) declareEncodable(OutgoingContentInfoMessageAttribute.self, f: { OutgoingContentInfoMessageAttribute(decoder: $0) }) declareEncodable(ConsumableContentMessageAttribute.self, f: { ConsumableContentMessageAttribute(decoder: $0) }) @@ -143,7 +203,6 @@ private var declaredEncodables: Void = { declareEncodable(CloudDocumentSizeMediaResource.self, f: { CloudDocumentSizeMediaResource(decoder: $0) }) declareEncodable(CloudPeerPhotoSizeMediaResource.self, f: { CloudPeerPhotoSizeMediaResource(decoder: $0) }) declareEncodable(CloudStickerPackThumbnailMediaResource.self, f: { CloudStickerPackThumbnailMediaResource(decoder: $0) }) - declareEncodable(AccountBackupDataAttribute.self, f: { AccountBackupDataAttribute(decoder: $0) }) declareEncodable(ContentRequiresValidationMessageAttribute.self, f: { ContentRequiresValidationMessageAttribute(decoder: $0) }) declareEncodable(WasScheduledMessageAttribute.self, f: { WasScheduledMessageAttribute(decoder: $0) }) declareEncodable(OutgoingScheduleInfoMessageAttribute.self, f: { OutgoingScheduleInfoMessageAttribute(decoder: $0) }) @@ -175,6 +234,9 @@ private var declaredEncodables: Void = { declareEncodable(CachedDisplayAsPeers.self, f: { CachedDisplayAsPeers(decoder: $0) }) 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 }() @@ -232,7 +294,7 @@ public final class TemporaryAccount { } } -public func temporaryAccount(manager: AccountManager, rootPath: String, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { +public func temporaryAccount(manager: AccountManager, rootPath: String, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { return manager.allocatedTemporaryAccountId() |> mapToSignal { id -> Signal in let path = "\(rootPath)/\(accountRecordIdPathName(id))" @@ -250,7 +312,7 @@ public func temporaryAccount(manager: AccountManager, rootPath: String, encrypti } } -public func currentAccount(allocateIfNotExists: Bool, networkArguments: NetworkInitializationArguments, supplementary: Bool, manager: AccountManager, rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { +public func currentAccount(allocateIfNotExists: Bool, networkArguments: NetworkInitializationArguments, supplementary: Bool, manager: AccountManager, rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { return manager.currentAccountRecord(allocateIfNotExists: allocateIfNotExists) |> distinctUntilChanged(isEqual: { lhs, rhs in return lhs?.0 == rhs?.0 @@ -261,7 +323,7 @@ public func currentAccount(allocateIfNotExists: Bool, networkArguments: NetworkI return reload.get() |> mapToSignal { _ -> Signal in let beginWithTestingEnvironment = record.1.contains(where: { attribute in - if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment { + if case let .environment(environment) = attribute, case .test = environment.environment { return true } else { return false @@ -314,7 +376,7 @@ public func currentAccount(allocateIfNotExists: Bool, networkArguments: NetworkI } } -public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManager, alreadyLoggedOutRemotely: Bool) -> Signal { +public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManager, alreadyLoggedOutRemotely: Bool) -> Signal { Logger.shared.log("AccountManager", "logoutFromAccount \(id)") return accountManager.transaction { transaction -> Void in transaction.updateRecord(id, { current in @@ -323,7 +385,7 @@ public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManage } else if let current = current { var found = false for attribute in current.attributes { - if attribute is LoggedOutAccountAttribute { + if case .loggedOut = attribute { found = true break } @@ -331,7 +393,7 @@ public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManage if found { return current } else { - return AccountRecord(id: current.id, attributes: current.attributes + [LoggedOutAccountAttribute()], temporarySessionId: nil) + return AccountRecord(id: current.id, attributes: current.attributes + [.loggedOut(LoggedOutAccountAttribute())], temporarySessionId: nil) } } else { return nil @@ -340,7 +402,7 @@ public func logoutFromAccount(id: AccountRecordId, accountManager: AccountManage } } -public func managedCleanupAccounts(networkArguments: NetworkInitializationArguments, accountManager: AccountManager, rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { +public func managedCleanupAccounts(networkArguments: NetworkInitializationArguments, accountManager: AccountManager, rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let currentTemporarySessionId = accountManager.temporarySessionId return Signal { subscriber in let loggedOutAccounts = Atomic<[AccountRecordId: MetaDisposable]>(value: [:]) @@ -355,12 +417,12 @@ public func managedCleanupAccounts(networkArguments: NetworkInitializationArgume }).start() let disposable = accountManager.accountRecords().start(next: { view in var disposeList: [(AccountRecordId, MetaDisposable)] = [] - var beginList: [(AccountRecordId, [AccountRecordAttribute], MetaDisposable)] = [] + var beginList: [(AccountRecordId, [TelegramAccountManagerTypes.Attribute], MetaDisposable)] = [] let _ = loggedOutAccounts.modify { disposables in - var validIds: [AccountRecordId: [AccountRecordAttribute]] = [:] + var validIds: [AccountRecordId: [TelegramAccountManagerTypes.Attribute]] = [:] outer: for record in view.records { for attribute in record.attributes { - if attribute is LoggedOutAccountAttribute { + if case .loggedOut = attribute { validIds[record.id] = record.attributes continue outer } @@ -425,9 +487,9 @@ public func managedCleanupAccounts(networkArguments: NetworkInitializationArgume } } -private func cleanupAccount(networkArguments: NetworkInitializationArguments, accountManager: AccountManager, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, attributes: [AccountRecordAttribute], rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal { +private func cleanupAccount(networkArguments: NetworkInitializationArguments, accountManager: AccountManager, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, attributes: [TelegramAccountManagerTypes.Attribute], rootPath: String, auxiliaryMethods: AccountAuxiliaryMethods) -> Signal { let beginWithTestingEnvironment = attributes.contains(where: { attribute in - if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment { + if case let .environment(accountEnvironment) = attribute, case .test = accountEnvironment.environment { return true } else { return false diff --git a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift new file mode 100644 index 0000000000..2c10fbe03c --- /dev/null +++ b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift @@ -0,0 +1,18 @@ +import Foundation +import Postbox + +public final class AdMessageAttribute: MessageAttribute { + public let opaqueId: Data + + public init(opaqueId: Data) { + self.opaqueId = opaqueId + } + + public init(decoder: PostboxDecoder) { + preconditionFailure() + } + + public func encode(_ encoder: PostboxEncoder) { + preconditionFailure() + } +} diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index b66812b3cb..1e15edb319 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -195,7 +195,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 035f2ab70b..df9db3bfb3 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -77,6 +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 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 2fb3916c78..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) @@ -46,21 +46,21 @@ extension TelegramBaseTheme { extension TelegramThemeSettings { convenience init?(apiThemeSettings: Api.ThemeSettings) { switch apiThemeSettings { - case let .themeSettings(_, baseTheme, accentColor, messageTopColor, messageBottomColor, wallpaper): - var messageColors: (UInt32, UInt32)? - if let messageTopColor = messageTopColor, let messageBottomColor = messageBottomColor { - messageColors = (UInt32(bitPattern: messageTopColor), UInt32(bitPattern: messageBottomColor)) - } - self.init(baseTheme: TelegramBaseTheme(apiBaseTheme: baseTheme), accentColor: UInt32(bitPattern: accentColor), messageColors: messageColors, wallpaper: wallpaper.flatMap(TelegramWallpaper.init(apiWallpaper:))) + case let .themeSettings(flags, baseTheme, accentColor, messageColors, wallpaper): + self.init(baseTheme: TelegramBaseTheme(apiBaseTheme: baseTheme), accentColor: UInt32(bitPattern: accentColor), messageColors: messageColors?.map(UInt32.init(bitPattern:)) ?? [], animateMessageColors: (flags & 1 << 2) != 0, wallpaper: wallpaper.flatMap(TelegramWallpaper.init(apiWallpaper:))) } } var apiInputThemeSettings: Api.InputThemeSettings { var flags: Int32 = 0 - if let _ = self.messageColors { + if !self.messageColors.isEmpty { flags |= 1 << 0 } + if self.animateMessageColors { + flags |= 1 << 2 + } + var inputWallpaper: Api.InputWallPaper? var inputWallpaperSettings: Api.WallPaperSettings? if let wallpaper = self.wallpaper, let inputWallpaperAndSettings = wallpaper.apiInputWallpaperAndSettings { @@ -69,15 +69,6 @@ extension TelegramThemeSettings { flags |= 1 << 1 } - var messageTopColor: Int32? - var messageBottomColor: Int32? - if let color = self.messageColors?.0 { - messageTopColor = Int32(bitPattern: color) - } - if let color = self.messageColors?.1 { - messageBottomColor = Int32(bitPattern: color) - } - - return .inputThemeSettings(flags: flags, baseTheme: self.baseTheme.apiBaseTheme, accentColor: Int32(bitPattern: self.accentColor), messageTopColor: messageTopColor, messageBottomColor: messageBottomColor, wallpaper: inputWallpaper, wallpaperSettings: inputWallpaperSettings) + return .inputThemeSettings(flags: flags, baseTheme: self.baseTheme.apiBaseTheme, accentColor: Int32(bitPattern: self.accentColor), messageColors: self.messageColors.isEmpty ? nil : self.messageColors.map(Int32.init(bitPattern:)), wallpaper: inputWallpaper, wallpaperSettings: inputWallpaperSettings) } } diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 9845a8d692..17dde9f772 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -14,17 +14,20 @@ public enum AuthorizationCodeRequestError { case timeout } -func switchToAuthorizedAccount(transaction: AccountManagerModifier, account: UnauthorizedAccount) { +func switchToAuthorizedAccount(transaction: AccountManagerModifier, account: UnauthorizedAccount) { let nextSortOrder = (transaction.getRecords().map({ record -> Int32 in for attribute in record.attributes { - if let attribute = attribute as? AccountSortOrderAttribute { - return attribute.order + if case let .sortOrder(sortOrder) = attribute { + return sortOrder.order } } return 0 }).max() ?? 0) + 1 transaction.updateRecord(account.id, { _ in - return AccountRecord(id: account.id, attributes: [AccountEnvironmentAttribute(environment: account.testingEnvironment ? .test : .production), AccountSortOrderAttribute(order: nextSortOrder)], temporarySessionId: nil) + return AccountRecord(id: account.id, attributes: [ + .environment(AccountEnvironmentAttribute(environment: account.testingEnvironment ? .test : .production)), + .sortOrder(AccountSortOrderAttribute(order: nextSortOrder)) + ], temporarySessionId: nil) }) transaction.setCurrentId(account.id) transaction.removeAuth() @@ -68,7 +71,7 @@ private func ~=(pattern: Regex, matchable: T) -> return matchable.match(pattern) } -public func sendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, syncContacts: Bool) -> Signal { +public func sendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, syncContacts: Bool) -> Signal { let sendCode = Api.functions.auth.sendCode(phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, settings: .codeSettings(flags: 0)) let codeAndAccount = account.network.request(sendCode, automaticFloodWait: false) @@ -202,7 +205,7 @@ public enum AuthorizeWithCodeResult { case loggedIn } -public func authorizeWithCode(accountManager: AccountManager, account: UnauthorizedAccount, code: String, termsOfService: UnauthorizedAccountTermsOfService?) -> Signal { +public func authorizeWithCode(accountManager: AccountManager, account: UnauthorizedAccount, code: String, termsOfService: UnauthorizedAccountTermsOfService?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let state = transaction.getState() as? UnauthorizedAccountState { switch state.contents { @@ -295,7 +298,7 @@ public enum AuthorizationPasswordVerificationError { case generic } -public func authorizeWithPassword(accountManager: AccountManager, account: UnauthorizedAccount, password: String, syncContacts: Bool) -> Signal { +public func authorizeWithPassword(accountManager: AccountManager, account: UnauthorizedAccount, password: String, syncContacts: Bool) -> Signal { return verifyPassword(account, password: password) |> `catch` { error -> Signal in if error.errorDescription.hasPrefix("FLOOD_WAIT") { @@ -372,7 +375,7 @@ public final class RecoveredAccountData { } } -public func loginWithRecoveredAccountData(accountManager: AccountManager, account: UnauthorizedAccount, recoveredAccountData: RecoveredAccountData, syncContacts: Bool) -> Signal { +public func loginWithRecoveredAccountData(accountManager: AccountManager, account: UnauthorizedAccount, recoveredAccountData: RecoveredAccountData, syncContacts: Bool) -> Signal { return account.postbox.transaction { transaction -> Signal in switch recoveredAccountData.authorization { case let .authorization(_, _, user): @@ -492,7 +495,7 @@ public enum SignUpError { case invalidLastName } -public func signUpWithName(accountManager: AccountManager, account: UnauthorizedAccount, firstName: String, lastName: String, avatarData: Data?, avatarVideo: Signal?, videoStartTimestamp: Double?) -> Signal { +public func signUpWithName(accountManager: AccountManager, account: UnauthorizedAccount, firstName: String, lastName: String, avatarData: Data?, avatarVideo: Signal?, videoStartTimestamp: Double?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let state = transaction.getState() as? UnauthorizedAccountState, case let .signUp(number, codeHash, _, _, _, syncContacts) = state.contents { return account.network.request(Api.functions.auth.signUp(phoneNumber: number, phoneCodeHash: codeHash, firstName: firstName, lastName: lastName)) @@ -525,8 +528,8 @@ public func signUpWithName(accountManager: AccountManager, account: Unauthorized let switchedAccounts = accountManager.transaction { transaction -> Void in switchToAuthorizedAccount(transaction: transaction, account: account) - } - |> castError(SignUpError.self) + } + |> castError(SignUpError.self) if let avatarData = avatarData { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift index 4f8872d0a3..9a32066ba9 100644 --- a/submodules/TelegramCore/Sources/Network/Network.swift +++ b/submodules/TelegramCore/Sources/Network/Network.swift @@ -1094,13 +1094,25 @@ class Keychain: NSObject, MTKeychain { } func setObject(_ object: Any!, forKey aKey: String!, group: String!) { - let data = NSKeyedArchiver.archivedData(withRootObject: object) - self.set(group + ":" + aKey, data) + guard let object = object else { + return + } + MTContext.perform(objCTry: { + let data = NSKeyedArchiver.archivedData(withRootObject: object) + self.set(group + ":" + aKey, data) + }) } func object(forKey aKey: String!, group: String!) -> Any! { + guard let aKey = aKey, let group = group else { + return nil + } if let data = self.get(group + ":" + aKey) { - return NSKeyedUnarchiver.unarchiveObject(with: data as Data) + var result: Any? + MTContext.perform(objCTry: { + result = NSKeyedUnarchiver.unarchiveObject(with: data as Data) + }) + return result } return nil } diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 85e8bd8a4e..4187acaec9 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -3,7 +3,6 @@ import Postbox import TelegramApi import SwiftSignalKit - public enum EnqueueMessageGrouping { case none case auto @@ -114,6 +113,8 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt return true case _ as EmojiSearchQueryMessageAttribute: return true + case _ as ForwardHideSendersNamesMessageAttribute: + return true default: return false } @@ -131,6 +132,8 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt return true case _ as OutgoingScheduleInfoMessageAttribute: return true + case _ as ForwardHideSendersNamesMessageAttribute: + return true default: return false } @@ -560,6 +563,14 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, var forwardInfo: StoreMessageForwardInfo? + var hideSendersNames = false + for attribute in requestedAttributes { + if let _ = attribute as? ForwardHideSendersNamesMessageAttribute { + hideSendersNames = true + break + } + } + if sourceMessage.id.namespace == Namespaces.Message.Cloud && peerId.namespace != Namespaces.Peer.SecretChat { attributes.append(ForwardSourceInfoAttribute(messageId: sourceMessage.id)) @@ -606,7 +617,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } } - if let sourceForwardInfo = sourceMessage.forwardInfo { + if hideSendersNames { + + } else if let sourceForwardInfo = sourceMessage.forwardInfo { forwardInfo = StoreMessageForwardInfo(authorId: sourceForwardInfo.author?.id, sourceId: sourceForwardInfo.source?.id, sourceMessageId: sourceForwardInfo.sourceMessageId, date: sourceForwardInfo.date, authorSignature: sourceForwardInfo.authorSignature, psaType: nil, flags: []) } else { if sourceMessage.id.peerId != account.peerId { diff --git a/submodules/TelegramCore/Sources/Settings/AutodownloadSettings.swift b/submodules/TelegramCore/Sources/Settings/AutodownloadSettings.swift index 513a055822..9d10248914 100644 --- a/submodules/TelegramCore/Sources/Settings/AutodownloadSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/AutodownloadSettings.swift @@ -4,7 +4,7 @@ import TelegramApi import SwiftSignalKit -public func updateAutodownloadSettingsInteractively(accountManager: AccountManager, _ f: @escaping (AutodownloadSettings) -> AutodownloadSettings) -> Signal { +public func updateAutodownloadSettingsInteractively(accountManager: AccountManager, _ f: @escaping (AutodownloadSettings) -> AutodownloadSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(SharedDataKeys.autodownloadSettings, { entry in let currentSettings: AutodownloadSettings diff --git a/submodules/TelegramCore/Sources/Settings/CacheStorageSettings.swift b/submodules/TelegramCore/Sources/Settings/CacheStorageSettings.swift index 6eaa896346..271caf26f6 100644 --- a/submodules/TelegramCore/Sources/Settings/CacheStorageSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/CacheStorageSettings.swift @@ -3,7 +3,7 @@ import Postbox import SwiftSignalKit -public func updateCacheStorageSettingsInteractively(accountManager: AccountManager, _ f: @escaping (CacheStorageSettings) -> CacheStorageSettings) -> Signal { +public func updateCacheStorageSettingsInteractively(accountManager: AccountManager, _ f: @escaping (CacheStorageSettings) -> CacheStorageSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(SharedDataKeys.cacheStorageSettings, { entry in let currentSettings: CacheStorageSettings diff --git a/submodules/TelegramCore/Sources/Settings/LoggingSettings.swift b/submodules/TelegramCore/Sources/Settings/LoggingSettings.swift index 40865e6458..f6e240824e 100644 --- a/submodules/TelegramCore/Sources/Settings/LoggingSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/LoggingSettings.swift @@ -3,7 +3,7 @@ import Postbox import SwiftSignalKit import MtProtoKit -public func updateLoggingSettings(accountManager: AccountManager, _ f: @escaping (LoggingSettings) -> LoggingSettings) -> Signal { +public func updateLoggingSettings(accountManager: AccountManager, _ f: @escaping (LoggingSettings) -> LoggingSettings) -> Signal { return accountManager.transaction { transaction -> Void in var updated: LoggingSettings? transaction.updateSharedData(SharedDataKeys.loggingSettings, { current in diff --git a/submodules/TelegramCore/Sources/Settings/ProxySettings.swift b/submodules/TelegramCore/Sources/Settings/ProxySettings.swift index 4f15b8df39..4316c1206a 100644 --- a/submodules/TelegramCore/Sources/Settings/ProxySettings.swift +++ b/submodules/TelegramCore/Sources/Settings/ProxySettings.swift @@ -3,7 +3,7 @@ import Postbox import SwiftSignalKit import MtProtoKit -public func updateProxySettingsInteractively(accountManager: AccountManager, _ f: @escaping (ProxySettings) -> ProxySettings) -> Signal { +public func updateProxySettingsInteractively(accountManager: AccountManager, _ f: @escaping (ProxySettings) -> ProxySettings) -> Signal { return accountManager.transaction { transaction -> Bool in return updateProxySettingsInteractively(transaction: transaction, f) } @@ -20,7 +20,7 @@ extension ProxyServerSettings { } } -public func updateProxySettingsInteractively(transaction: AccountManagerModifier, _ f: @escaping (ProxySettings) -> ProxySettings) -> Bool { +public func updateProxySettingsInteractively(transaction: AccountManagerModifier, _ f: @escaping (ProxySettings) -> ProxySettings) -> Bool { var hasChanges = false transaction.updateSharedData(SharedDataKeys.proxySettings, { current in let previous = (current as? ProxySettings) ?? ProxySettings.defaultSettings diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index a6c066ec6b..af05b9a6cf 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: @@ -2255,7 +2253,7 @@ private func recordPeerActivityTimestamp(peerId: PeerId, timestamp: Int32, into } } -func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountPeerId: PeerId, mediaBox: MediaBox, encryptionProvider: EncryptionProvider, transaction: Transaction, auxiliaryMethods: AccountAuxiliaryMethods, finalState: AccountFinalState, removePossiblyDeliveredMessagesUniqueIds: [Int64: PeerId]) -> AccountReplayedFinalState? { +func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountPeerId: PeerId, mediaBox: MediaBox, encryptionProvider: EncryptionProvider, transaction: Transaction, auxiliaryMethods: AccountAuxiliaryMethods, finalState: AccountFinalState, removePossiblyDeliveredMessagesUniqueIds: [Int64: PeerId]) -> AccountReplayedFinalState? { let verified = verifyTransaction(transaction, finalState: finalState.state) if !verified { Logger.shared.log("State", "failed to verify final state") @@ -2449,6 +2447,28 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP }) } switch action.action { + case let .setChatTheme(emoji): + transaction.updatePeerCachedData(peerIds: [message.id.peerId], update: { peerId, current in + var current = current + if current == nil { + if peerId.namespace == Namespaces.Peer.CloudUser { + current = CachedUserData() + } else if peerId.namespace == Namespaces.Peer.CloudGroup { + current = CachedGroupData() + } else if peerId.namespace == Namespaces.Peer.CloudChannel { + current = CachedChannelData() + } + } + if let cachedData = current as? CachedUserData { + return cachedData.withUpdatedThemeEmoticon(!emoji.isEmpty ? emoji : nil) + } else if let cachedData = current as? CachedGroupData { + return cachedData.withUpdatedThemeEmoticon(!emoji.isEmpty ? emoji : nil) + } else if let cachedData = current as? CachedChannelData { + return cachedData.withUpdatedThemeEmoticon(!emoji.isEmpty ? emoji : nil) + } else { + return current + } + }) case .groupCreated, .channelMigratedFromGroup: let holesAtHistoryStart = transaction.getHole(containing: MessageId(peerId: chatPeerId, namespace: Namespaces.Message.Cloud, id: id.id - 1)) for (space, _) in holesAtHistoryStart { diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index f619487341..b1416aad0f 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -53,7 +53,7 @@ public enum DeletedMessageId: Hashable { public final class AccountStateManager { private let queue = Queue() private let accountPeerId: PeerId - private let accountManager: AccountManager + private let accountManager: AccountManager private let postbox: Postbox private let network: Network private let callSessionManager: CallSessionManager @@ -166,7 +166,7 @@ public final class AccountStateManager { private let appliedQtsPromise = Promise(nil) private let appliedQtsDisposable = MetaDisposable() - init(accountPeerId: PeerId, accountManager: AccountManager, postbox: Postbox, network: Network, callSessionManager: CallSessionManager, addIsContactUpdates: @escaping ([(PeerId, Bool)]) -> Void, shouldKeepOnlinePresence: Signal, peerInputActivityManager: PeerInputActivityManager, auxiliaryMethods: AccountAuxiliaryMethods) { + init(accountPeerId: PeerId, accountManager: AccountManager, postbox: Postbox, network: Network, callSessionManager: CallSessionManager, addIsContactUpdates: @escaping ([(PeerId, Bool)]) -> Void, shouldKeepOnlinePresence: Signal, peerInputActivityManager: PeerInputActivityManager, auxiliaryMethods: AccountAuxiliaryMethods) { self.accountPeerId = accountPeerId self.accountManager = accountManager self.postbox = postbox diff --git a/submodules/TelegramCore/Sources/State/ManagedAutodownloadSettingsUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedAutodownloadSettingsUpdates.swift index 4de3ecf5d3..ab86a5bd7b 100644 --- a/submodules/TelegramCore/Sources/State/ManagedAutodownloadSettingsUpdates.swift +++ b/submodules/TelegramCore/Sources/State/ManagedAutodownloadSettingsUpdates.swift @@ -5,7 +5,7 @@ import TelegramApi import MtProtoKit -func managedAutodownloadSettingsUpdates(accountManager: AccountManager, network: Network) -> Signal { +func managedAutodownloadSettingsUpdates(accountManager: AccountManager, network: Network) -> Signal { let poll = Signal { subscriber in return (network.request(Api.functions.account.getAutoDownloadSettings()) |> retryRequest diff --git a/submodules/TelegramCore/Sources/State/ManagedConfigurationUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedConfigurationUpdates.swift index 3ed8038358..2453192284 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConfigurationUpdates.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConfigurationUpdates.swift @@ -5,7 +5,7 @@ import TelegramApi import MtProtoKit -func managedConfigurationUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { +func managedConfigurationUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { let poll = Signal { subscriber in return (network.request(Api.functions.help.getConfig()) |> retryRequest diff --git a/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift b/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift index 85efec9b45..f06531a28b 100644 --- a/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift +++ b/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift @@ -129,6 +129,8 @@ private func actionFromActivity(_ activity: PeerInputActivity?) -> Api.SendMessa return .sendMessageUploadRoundAction(progress: progress) case .speakingInGroupCall: return .speakingInGroupCallAction + case .choosingSticker: + return .sendMessageChooseStickerAction } } else { return .sendMessageCancelAction diff --git a/submodules/TelegramCore/Sources/State/ManagedLocalizationUpdatesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedLocalizationUpdatesOperations.swift index fa6f69621d..182f8787a3 100644 --- a/submodules/TelegramCore/Sources/State/ManagedLocalizationUpdatesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedLocalizationUpdatesOperations.swift @@ -65,7 +65,7 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOpera } |> switchToLatest } -func managedLocalizationUpdatesOperations(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { +func managedLocalizationUpdatesOperations(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { return Signal { _ in let tag: PeerOperationLogTag = OperationLogTags.SynchronizeLocalizationUpdates @@ -116,7 +116,7 @@ private enum SynchronizeLocalizationUpdatesError { case reset } -func getLocalization(_ transaction: AccountManagerModifier) -> (primary: (code: String, version: Int32, entries: [LocalizationEntry]), secondary: (code: String, version: Int32, entries: [LocalizationEntry])?) { +func getLocalization(_ transaction: AccountManagerModifier) -> (primary: (code: String, version: Int32, entries: [LocalizationEntry]), secondary: (code: String, version: Int32, entries: [LocalizationEntry])?) { let localizationSettings: LocalizationSettings? if let current = transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings { localizationSettings = current @@ -148,7 +148,7 @@ private func parseLangPackDifference(_ difference: Api.LangPackDifference) -> (c } } -private func synchronizeLocalizationUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { +private func synchronizeLocalizationUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { let currentLanguageAndVersion = accountManager.transaction { transaction -> (primary: (code: String, version: Int32), secondary: (code: String, version: Int32)?) in let (primary, secondary) = getLocalization(transaction) return ((primary.code, primary.version), secondary.flatMap({ ($0.code, $0.version) })) @@ -240,7 +240,7 @@ private func synchronizeLocalizationUpdates(accountManager: AccountManager, post } } -func tryApplyingLanguageDifference(transaction: AccountManagerModifier, langCode: String, difference: Api.LangPackDifference) -> Bool { +func tryApplyingLanguageDifference(transaction: AccountManagerModifier, langCode: String, difference: Api.LangPackDifference) -> Bool { let (primary, secondary) = getLocalization(transaction) switch difference { case let .langPackDifference(updatedCode, fromVersion, updatedVersion, strings): diff --git a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift index 611ee31e27..eff6f99446 100644 --- a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift +++ b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift @@ -11,6 +11,7 @@ public enum PeerInputActivity: Comparable { case recordingInstantVideo case uploadingInstantVideo(progress: Int32) case speakingInGroupCall(timestamp: Int32) + case choosingSticker public var key: Int32 { switch self { @@ -32,6 +33,8 @@ public enum PeerInputActivity: Comparable { return 7 case .playingGame: return 8 + case .choosingSticker: + return 9 } } @@ -63,7 +66,9 @@ extension PeerInputActivity { self = .uploadingInstantVideo(progress: progress) case .speakingInGroupCallAction: self = .speakingInGroupCall(timestamp: timestamp) - case let .sendMessageHistoryImportAction: + case .sendMessageChooseStickerAction: + self = .choosingSticker + case .sendMessageHistoryImportAction: return nil } } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 9e5b584ad5..8c0eff0038 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -697,6 +697,7 @@ public final class PendingMessageManager { return .complete() } else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { var isForward = false + var hideSendersNames = false var replyMessageId: Int32? var scheduleTime: Int32? @@ -714,6 +715,8 @@ public final class PendingMessageManager { } else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { flags |= Int32(1 << 10) scheduleTime = attribute.scheduleTime + } else if let _ = attribute as? ForwardHideSendersNamesMessageAttribute { + hideSendersNames = true } } @@ -722,6 +725,9 @@ public final class PendingMessageManager { if messages.contains(where: { $0.0.groupingKey != nil }) { flags |= (1 << 9) } + if hideSendersNames { + flags |= (1 << 11) + } var forwardIds: [(MessageId, Int64)] = [] for (message, content) in messages { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountBackupDataAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountBackupDataAttribute.swift index e2c90facfa..d5fcbd4c51 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountBackupDataAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountBackupDataAttribute.swift @@ -15,32 +15,34 @@ public struct AccountBackupData: Codable, Equatable { } } -public final class AccountBackupDataAttribute: AccountRecordAttribute, Equatable { +public final class AccountBackupDataAttribute: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case data + } + public let data: AccountBackupData? public init(data: AccountBackupData?) { self.data = data } - - public init(decoder: PostboxDecoder) { - self.data = try? JSONDecoder().decode(AccountBackupData.self, from: decoder.decodeDataForKey("data") ?? Data()) + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.data = try? JSONDecoder().decode(AccountBackupData.self, from: (try? container.decode(Data.self, forKey: .data)) ?? Data()) } - public func encode(_ encoder: PostboxEncoder) { + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if let data = self.data, let serializedData = try? JSONEncoder().encode(data) { - encoder.encodeData(serializedData, forKey: "data") + try container.encode(serializedData, forKey: .data) + } else { + try container.encodeNil(forKey: .data) } } public static func ==(lhs: AccountBackupDataAttribute, rhs: AccountBackupDataAttribute) -> Bool { return lhs.data == rhs.data } - - public func isEqual(to: AccountRecordAttribute) -> Bool { - if let to = to as? AccountBackupDataAttribute { - return self == to - } else { - return false - } - } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountEnvironmentAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountEnvironmentAttribute.swift index dda33baa2f..3355aa0291 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountEnvironmentAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountEnvironmentAttribute.swift @@ -6,26 +6,33 @@ public enum AccountEnvironment: Int32 { case test = 1 } -public final class AccountEnvironmentAttribute: AccountRecordAttribute { +public final class AccountEnvironmentAttribute: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case environment + } + public let environment: AccountEnvironment public init(environment: AccountEnvironment) { self.environment = environment } - public init(decoder: PostboxDecoder) { - self.environment = AccountEnvironment(rawValue: decoder.decodeInt32ForKey("environment", orElse: 0)) ?? .production + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let environmentValue: Int32 = (try? container.decode(Int32.self, forKey: .environment)) ?? 0 + + self.environment = AccountEnvironment(rawValue: environmentValue) ?? .production } - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt32(self.environment.rawValue, forKey: "environment") + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.environment.rawValue, forKey: .environment) } - - public func isEqual(to: AccountRecordAttribute) -> Bool { - guard let to = to as? AccountEnvironmentAttribute else { - return false - } - if self.environment != to.environment { + + public static func ==(lhs: AccountEnvironmentAttribute, rhs: AccountEnvironmentAttribute) -> Bool { + if lhs.environment != rhs.environment { return false } return true diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountSortOrderAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountSortOrderAttribute.swift index 39ebfb4c92..0f19cf0e01 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountSortOrderAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AccountSortOrderAttribute.swift @@ -1,26 +1,31 @@ import Foundation import Postbox -public final class AccountSortOrderAttribute: AccountRecordAttribute { +public final class AccountSortOrderAttribute: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case order + } + public let order: Int32 public init(order: Int32) { self.order = order } - public init(decoder: PostboxDecoder) { - self.order = decoder.decodeInt32ForKey("order", orElse: 0) + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.order = (try? container.decode(Int32.self, forKey: .order)) ?? 0 } - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt32(self.order, forKey: "order") + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.order, forKey: .order) } - - public func isEqual(to: AccountRecordAttribute) -> Bool { - guard let to = to as? AccountSortOrderAttribute else { - return false - } - if self.order != to.order { + + public static func ==(lhs: AccountSortOrderAttribute, rhs: AccountSortOrderAttribute) -> Bool { + if lhs.order != rhs.order { return false } return true diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AuthorSignatureMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AuthorSignatureMessageAttribute.swift index aa42b748be..3971bfd00f 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AuthorSignatureMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AuthorSignatureMessageAttribute.swift @@ -1,7 +1,6 @@ import Foundation import Postbox - public class AuthorSignatureMessageAttribute: MessageAttribute { public let signature: String 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_ForwardHideSendersNamesMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ForwardHideSendersNamesMessageAttribute.swift new file mode 100644 index 0000000000..de6a9a3ba3 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ForwardHideSendersNamesMessageAttribute.swift @@ -0,0 +1,13 @@ +import Foundation +import Postbox + +public class ForwardHideSendersNamesMessageAttribute: MessageAttribute { + public init() { + } + + required public init(decoder: PostboxDecoder) { + } + + public func encode(_ encoder: PostboxEncoder) { + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_LoggedOutAccountAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_LoggedOutAccountAttribute.swift index 5e17ab5c76..7614eb220f 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_LoggedOutAccountAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_LoggedOutAccountAttribute.swift @@ -1,17 +1,17 @@ import Foundation import Postbox -public final class LoggedOutAccountAttribute: AccountRecordAttribute { +public final class LoggedOutAccountAttribute: Codable, Equatable { public init() { } - public init(decoder: PostboxDecoder) { + public init(from decoder: Decoder) throws { } - public func encode(_ encoder: PostboxEncoder) { + public func encode(to encoder: Encoder) throws { } - - public func isEqual(to: AccountRecordAttribute) -> Bool { - return to is LoggedOutAccountAttribute + + public static func ==(lhs: LoggedOutAccountAttribute, rhs: LoggedOutAccountAttribute) -> Bool { + return true } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 61cde874e7..3a57dba083 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -76,6 +76,7 @@ public struct Namespaces { public static let cachedPeerInvitationImporters: Int8 = 12 public static let cachedPeerExportedInvitations: Int8 = 13 public static let cachedGroupCallDisplayAsPeers: Int8 = 14 + public static let cachedAdMessageStates: Int8 = 15 } public struct UnorderedItemList { @@ -350,6 +351,7 @@ private enum SharedDataKeyValues: Int32 { case themeSettings = 6 case countriesList = 7 case wallapersState = 8 + case chatThemes = 9 } public struct SharedDataKeys { @@ -400,6 +402,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/SyncCore/SyncCore_TelegramTheme.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramTheme.swift index 8245179787..2cdd71be2d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramTheme.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramTheme.swift @@ -21,7 +21,10 @@ public final class TelegramThemeSettings: PostboxCoding, Equatable { if lhs.accentColor != rhs.accentColor { return false } - if lhs.messageColors?.0 != rhs.messageColors?.0 || lhs.messageColors?.1 != rhs.messageColors?.1 { + if lhs.messageColors != rhs.messageColors { + return false + } + if lhs.animateMessageColors != rhs.animateMessageColors { return false } if lhs.wallpaper != rhs.wallpaper { @@ -32,37 +35,40 @@ public final class TelegramThemeSettings: PostboxCoding, Equatable { public let baseTheme: TelegramBaseTheme public let accentColor: UInt32 - public let messageColors: (top: UInt32, bottom: UInt32)? + public let messageColors: [UInt32] + public let animateMessageColors: Bool public let wallpaper: TelegramWallpaper? - public init(baseTheme: TelegramBaseTheme, accentColor: UInt32, messageColors: (top: UInt32, bottom: UInt32)?, wallpaper: TelegramWallpaper?) { + public init(baseTheme: TelegramBaseTheme, accentColor: UInt32, messageColors: [UInt32], animateMessageColors: Bool, wallpaper: TelegramWallpaper?) { self.baseTheme = baseTheme self.accentColor = accentColor self.messageColors = messageColors + self.animateMessageColors = animateMessageColors self.wallpaper = wallpaper } public init(decoder: PostboxDecoder) { self.baseTheme = TelegramBaseTheme(rawValue: decoder.decodeInt32ForKey("baseTheme", orElse: 0)) ?? .classic self.accentColor = UInt32(bitPattern: decoder.decodeInt32ForKey("accent", orElse: 0)) - if let topMessageColor = decoder.decodeOptionalInt32ForKey("topMessage"), let bottomMessageColor = decoder.decodeOptionalInt32ForKey("bottomMessage") { - self.messageColors = (UInt32(bitPattern: topMessageColor), UInt32(bitPattern: bottomMessageColor)) + let messageColors = decoder.decodeInt32ArrayForKey("messageColors") + if !messageColors.isEmpty { + self.messageColors = messageColors.map(UInt32.init(bitPattern:)) } else { - self.messageColors = nil + if let topMessageColor = decoder.decodeOptionalInt32ForKey("topMessage"), let bottomMessageColor = decoder.decodeOptionalInt32ForKey("bottomMessage") { + self.messageColors = [UInt32(bitPattern: topMessageColor), UInt32(bitPattern: bottomMessageColor)] + } else { + self.messageColors = [] + } } + self.animateMessageColors = decoder.decodeInt32ForKey("animateMessageColors", orElse: 0) != 0 self.wallpaper = decoder.decodeObjectForKey("wallpaper", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.baseTheme.rawValue, forKey: "baseTheme") encoder.encodeInt32(Int32(bitPattern: self.accentColor), forKey: "accent") - if let (topMessageColor, bottomMessageColor) = self.messageColors { - encoder.encodeInt32(Int32(bitPattern: topMessageColor), forKey: "topMessage") - encoder.encodeInt32(Int32(bitPattern: bottomMessageColor), forKey: "bottomMessage") - } else { - encoder.encodeNil(forKey: "topMessage") - encoder.encodeNil(forKey: "bottomMessage") - } + encoder.encodeInt32Array(self.messageColors.map(Int32.init(bitPattern:)), forKey: "messageColors") + encoder.encodeInt32(self.animateMessageColors ? 1 : 0, forKey: "animateMessageColors") if let wallpaper = self.wallpaper { encoder.encodeObject(wallpaper, forKey: "wallpaper") } else { @@ -80,9 +86,9 @@ public final class TelegramTheme: OrderedItemListEntryContents, Equatable { public let settings: TelegramThemeSettings? public let isCreator: Bool public let isDefault: Bool - public let installCount: Int32 + public let installCount: Int32? - public init(id: Int64, accessHash: Int64, slug: String, title: String, file: TelegramMediaFile?, settings: TelegramThemeSettings?, isCreator: Bool, isDefault: Bool, installCount: Int32) { + public init(id: Int64, accessHash: Int64, slug: String, title: String, file: TelegramMediaFile?, settings: TelegramThemeSettings?, isCreator: Bool, isDefault: Bool, installCount: Int32?) { self.id = id self.accessHash = accessHash self.slug = slug @@ -103,7 +109,7 @@ public final class TelegramTheme: OrderedItemListEntryContents, Equatable { self.settings = decoder.decodeObjectForKey("settings", decoder: { TelegramThemeSettings(decoder: $0) }) as? TelegramThemeSettings self.isCreator = decoder.decodeInt32ForKey("isCreator", orElse: 0) != 0 self.isDefault = decoder.decodeInt32ForKey("isDefault", orElse: 0) != 0 - self.installCount = decoder.decodeInt32ForKey("installCount", orElse: 0) + self.installCount = decoder.decodeOptionalInt32ForKey("installCount") } public func encode(_ encoder: PostboxEncoder) { @@ -123,7 +129,11 @@ public final class TelegramTheme: OrderedItemListEntryContents, Equatable { } encoder.encodeInt32(self.isCreator ? 1 : 0, forKey: "isCreator") encoder.encodeInt32(self.isDefault ? 1 : 0, forKey: "isDefault") - encoder.encodeInt32(self.installCount, forKey: "installCount") + if let installCount = self.installCount { + encoder.encodeInt32(installCount, forKey: "installCount") + } else { + encoder.encodeNil(forKey: "installCount") + } } public static func ==(lhs: TelegramTheme, rhs: TelegramTheme) -> Bool { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_wallapersState.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_wallapersState.swift index b3117097fa..7d7a6af064 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_wallapersState.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_wallapersState.swift @@ -26,7 +26,7 @@ public struct WallpapersState: PreferencesEntry, Equatable { } public extension WallpapersState { - static func update(transaction: AccountManagerModifier, _ f: (WallpapersState) -> WallpapersState) { + static func update(transaction: AccountManagerModifier, _ f: (WallpapersState) -> WallpapersState) { transaction.updateSharedData(SharedDataKeys.wallapersState, { current in let item = (transaction.getSharedData(SharedDataKeys.wallapersState) as? WallpapersState) ?? WallpapersState(wallpapers: []) return f(item) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Auth/AuthTransfer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Auth/AuthTransfer.swift index a83a302f59..21ceaf85e8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Auth/AuthTransfer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Auth/AuthTransfer.swift @@ -20,7 +20,7 @@ public enum ExportAuthTransferTokenResult { case passwordRequested(UnauthorizedAccount) } -func _internal_exportAuthTransferToken(accountManager: AccountManager, account: UnauthorizedAccount, otherAccountUserIds: [PeerId.Id], syncContacts: Bool) -> Signal { +func _internal_exportAuthTransferToken(accountManager: AccountManager, account: UnauthorizedAccount, otherAccountUserIds: [PeerId.Id], syncContacts: Bool) -> Signal { return account.network.request(Api.functions.auth.exportLoginToken(apiId: account.networkArguments.apiId, apiHash: account.networkArguments.apiHash, exceptIds: otherAccountUserIds.map({ $0._internalGetInt64Value() }))) |> map(Optional.init) |> `catch` { error -> Signal in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift b/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift index fe4ebd29cb..307023ceaf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift @@ -11,7 +11,7 @@ public extension TelegramEngineUnauthorized { self.account = account } - public func exportAuthTransferToken(accountManager: AccountManager, otherAccountUserIds: [PeerId.Id], syncContacts: Bool) -> Signal { + public func exportAuthTransferToken(accountManager: AccountManager, otherAccountUserIds: [PeerId.Id], syncContacts: Bool) -> Signal { return _internal_exportAuthTransferToken(accountManager: accountManager, account: self.account, otherAccountUserIds: otherAccountUserIds, syncContacts: syncContacts) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index e0c836a889..884c143858 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -80,7 +80,7 @@ public enum GetCurrentGroupCallError { } func _internal_getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal { - return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), limit: 3)) + return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), limit: 4)) |> mapError { _ -> GetCurrentGroupCallError in return .generic } @@ -1981,7 +1981,7 @@ public final class GroupCallParticipantsContext { self.updateMuteState(peerId: self.myPeerId, muteState: nil, volume: nil, raiseHand: false) } - public func updateShouldBeRecording(_ shouldBeRecording: Bool, title: String?) { + public func updateShouldBeRecording(_ shouldBeRecording: Bool, title: String?, videoOrientation: Bool?) { var flags: Int32 = 0 if shouldBeRecording { flags |= 1 << 0 @@ -1989,7 +1989,13 @@ public final class GroupCallParticipantsContext { if let title = title, !title.isEmpty { flags |= (1 << 1) } - self.updateShouldBeRecordingDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallRecord(flags: flags, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), title: title)) + var videoPortrait: Api.Bool? + if let videoOrientation = videoOrientation { + flags |= (1 << 2) + videoPortrait = videoOrientation ? .boolTrue : .boolFalse + } + + self.updateShouldBeRecordingDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallRecord(flags: flags, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), title: title, videoPortrait: videoPortrait)) |> deliverOnMainQueue).start(next: { [weak self] updates in guard let strongSelf = self else { return @@ -2233,7 +2239,7 @@ func _internal_groupCallDisplayAsAvailablePeers(network: Network, postbox: Postb for chat in chats { if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { switch chat { - case let .channel(_, _, _, _, _, _, _, _, _, _, _, participantsCount): + case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount): if let participantsCount = participantsCount { subscribers[groupOrChannel.id] = participantsCount } @@ -2363,7 +2369,7 @@ public final class AudioBroadcastDataSource { } func _internal_getAudioBroadcastDataSource(account: Account, callId: Int64, accessHash: Int64) -> Signal { - return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), limit: 3)) + return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), limit: 4)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -2396,6 +2402,11 @@ public struct GetAudioBroadcastPartResult { public var status: Status public var responseTimestamp: Double + + public init(status: Status, responseTimestamp: Double) { + self.status = status + self.responseTimestamp = responseTimestamp + } } func _internal_getAudioBroadcastPart(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64, timestampIdMilliseconds: Int64, durationMilliseconds: Int64) -> Signal { @@ -2409,7 +2420,58 @@ func _internal_getAudioBroadcastPart(dataSource: AudioBroadcastDataSource, callI return .single(GetAudioBroadcastPartResult(status: .notReady, responseTimestamp: Double(timestampIdMilliseconds) / 1000.0)) } - return dataSource.download.requestWithAdditionalData(Api.functions.upload.getFile(flags: 0, location: .inputGroupCallStream(call: .inputGroupCall(id: callId, accessHash: accessHash), timeMs: timestampIdMilliseconds, scale: scale), offset: 0, limit: 128 * 1024), automaticFloodWait: false, failOnServerErrors: true) + return dataSource.download.requestWithAdditionalData(Api.functions.upload.getFile(flags: 0, location: .inputGroupCallStream(flags: 0, call: .inputGroupCall(id: callId, accessHash: accessHash), timeMs: timestampIdMilliseconds, scale: scale, videoChannel: nil, videoQuality: nil), offset: 0, limit: 128 * 1024), automaticFloodWait: false, failOnServerErrors: true) + |> map { result, responseTimestamp -> GetAudioBroadcastPartResult in + switch result { + case let .file(_, _, bytes): + return GetAudioBroadcastPartResult( + status: .data(bytes.makeData()), + responseTimestamp: responseTimestamp + ) + case .fileCdnRedirect: + return GetAudioBroadcastPartResult( + status: .notReady, + responseTimestamp: responseTimestamp + ) + } + } + |> `catch` { error, responseTimestamp -> Signal in + if error.errorDescription == "GROUPCALL_JOIN_MISSING" { + return .single(GetAudioBroadcastPartResult( + status: .rejoinNeeded, + responseTimestamp: responseTimestamp + )) + } else if error.errorDescription.hasPrefix("FLOOD_WAIT") || error.errorDescription == "TIME_TOO_BIG" { + return .single(GetAudioBroadcastPartResult( + status: .notReady, + responseTimestamp: responseTimestamp + )) + } else if error.errorDescription == "TIME_INVALID" || error.errorDescription == "TIME_TOO_SMALL" { + return .single(GetAudioBroadcastPartResult( + status: .resyncNeeded, + responseTimestamp: responseTimestamp + )) + } else { + return .single(GetAudioBroadcastPartResult( + status: .resyncNeeded, + responseTimestamp: responseTimestamp + )) + } + } +} + +func _internal_getVideoBroadcastPart(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64, timestampIdMilliseconds: Int64, durationMilliseconds: Int64, channelId: Int32, quality: Int32) -> Signal { + let scale: Int32 + switch durationMilliseconds { + case 1000: + scale = 0 + case 500: + scale = 1 + default: + return .single(GetAudioBroadcastPartResult(status: .notReady, responseTimestamp: Double(timestampIdMilliseconds) / 1000.0)) + } + + return dataSource.download.requestWithAdditionalData(Api.functions.upload.getFile(flags: 0, location: .inputGroupCallStream(flags: 1 << 0, call: .inputGroupCall(id: callId, accessHash: accessHash), timeMs: timestampIdMilliseconds, scale: scale, videoChannel: channelId, videoQuality: quality), offset: 0, limit: 512 * 1024), automaticFloodWait: false, failOnServerErrors: true) |> map { result, responseTimestamp -> GetAudioBroadcastPartResult in switch result { case let .file(_, _, bytes): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift index 7e734808ab..b365a00f66 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift @@ -104,6 +104,10 @@ public extension TelegramEngine { return _internal_getAudioBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds) } + public func getVideoBroadcastPart(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64, timestampIdMilliseconds: Int64, durationMilliseconds: Int64, channelId: Int32, quality: Int32) -> Signal { + return _internal_getVideoBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds, channelId: channelId, quality: quality) + } + public func groupCall(peerId: PeerId, myPeerId: PeerId, id: Int64, accessHash: Int64, state: GroupCallParticipantsContext.State, previousServiceState: GroupCallParticipantsContext.ServiceState?) -> GroupCallParticipantsContext { return GroupCallParticipantsContext(account: self.account, peerId: peerId, myPeerId: myPeerId, id: id, accessHash: accessHash, state: state, previousServiceState: previousServiceState) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/Configuration.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/Configuration.swift new file mode 100644 index 0000000000..48f702ccad --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/Configuration.swift @@ -0,0 +1,93 @@ +import SwiftSignalKit +import Postbox + +public enum EngineConfiguration { + public struct Limits: Equatable { + public static let timeIntervalForever: Int32 = 0x7fffffff + + public var maxPinnedChatCount: Int32 + public var maxArchivedPinnedChatCount: Int32 + public var maxGroupMemberCount: Int32 + public var maxSupergroupMemberCount: Int32 + public var maxMessageForwardBatchSize: Int32 + public var maxSavedGifCount: Int32 + public var maxRecentStickerCount: Int32 + public var maxMessageEditingInterval: Int32 + public var maxMediaCaptionLength: Int32 + public var canRemoveIncomingMessagesInPrivateChats: Bool + public var maxMessageRevokeInterval: Int32 + public var maxMessageRevokeIntervalInPrivateChats: Int32 + + public init( + maxPinnedChatCount: Int32, + maxArchivedPinnedChatCount: Int32, + maxGroupMemberCount: Int32, + maxSupergroupMemberCount: Int32, + maxMessageForwardBatchSize: Int32, + maxSavedGifCount: Int32, + maxRecentStickerCount: Int32, + maxMessageEditingInterval: Int32, + maxMediaCaptionLength: Int32, + canRemoveIncomingMessagesInPrivateChats: Bool, + maxMessageRevokeInterval: Int32, + maxMessageRevokeIntervalInPrivateChats: Int32 + ) { + self.maxPinnedChatCount = maxPinnedChatCount + self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount + self.maxGroupMemberCount = maxGroupMemberCount + self.maxSupergroupMemberCount = maxSupergroupMemberCount + self.maxMessageForwardBatchSize = maxMessageForwardBatchSize + self.maxSavedGifCount = maxSavedGifCount + self.maxRecentStickerCount = maxRecentStickerCount + self.maxMessageEditingInterval = maxMessageEditingInterval + self.maxMediaCaptionLength = maxMediaCaptionLength + self.canRemoveIncomingMessagesInPrivateChats = canRemoveIncomingMessagesInPrivateChats + self.maxMessageRevokeInterval = maxMessageRevokeInterval + self.maxMessageRevokeIntervalInPrivateChats = maxMessageRevokeIntervalInPrivateChats + } + } +} + +extension EngineConfiguration.Limits { + init(_ limitsConfiguration: LimitsConfiguration) { + self.init( + maxPinnedChatCount: limitsConfiguration.maxPinnedChatCount, + maxArchivedPinnedChatCount: limitsConfiguration.maxArchivedPinnedChatCount, + maxGroupMemberCount: limitsConfiguration.maxGroupMemberCount, + maxSupergroupMemberCount: limitsConfiguration.maxSupergroupMemberCount, + maxMessageForwardBatchSize: limitsConfiguration.maxMessageForwardBatchSize, + maxSavedGifCount: limitsConfiguration.maxSavedGifCount, + maxRecentStickerCount: limitsConfiguration.maxRecentStickerCount, + maxMessageEditingInterval: limitsConfiguration.maxMessageEditingInterval, + maxMediaCaptionLength: limitsConfiguration.maxMediaCaptionLength, + canRemoveIncomingMessagesInPrivateChats: limitsConfiguration.canRemoveIncomingMessagesInPrivateChats, + maxMessageRevokeInterval: limitsConfiguration.maxMessageRevokeInterval, + maxMessageRevokeIntervalInPrivateChats: limitsConfiguration.maxMessageRevokeIntervalInPrivateChats + ) + } +} + +public extension TelegramEngine.EngineData.Item { + enum Configuration { + public struct Limits: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = EngineConfiguration.Limits + + public init() { + } + + var key: PostboxViewKey { + return .preferences(keys: Set([PreferencesKeys.limitsConfiguration])) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? PreferencesView else { + preconditionFailure() + } + guard let limitsConfiguration = view.values[PreferencesKeys.limitsConfiguration] as? LimitsConfiguration else { + return EngineConfiguration.Limits(LimitsConfiguration.defaultValue) + } + return EngineConfiguration.Limits(limitsConfiguration) + } + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Localization/Countries.swift b/submodules/TelegramCore/Sources/TelegramEngine/Localization/Countries.swift index 75bb8fa88b..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 @@ -101,7 +100,7 @@ public final class CountriesList: PreferencesEntry, Equatable { } -func _internal_getCountriesList(accountManager: AccountManager, network: Network, langCode: String?, forceUpdate: Bool = false) -> Signal<[Country], NoError> { +func _internal_getCountriesList(accountManager: AccountManager, network: Network, langCode: String?, forceUpdate: Bool = false) -> Signal<[Country], NoError> { let fetch: ([Country]?, Int32?) -> Signal<[Country], NoError> = { current, hash in return network.request(Api.functions.help.getCountriesList(langCode: langCode ?? "", hash: hash ?? 0)) |> retryRequest diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Localization/Localizations.swift b/submodules/TelegramCore/Sources/TelegramEngine/Localization/Localizations.swift index 606a10cb3a..845530e01d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Localization/Localizations.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Localization/Localizations.swift @@ -99,7 +99,7 @@ public enum DownloadAndApplyLocalizationError { case generic } -func _internal_downloadAndApplyLocalization(accountManager: AccountManager, postbox: Postbox, network: Network, languageCode: String) -> Signal { +func _internal_downloadAndApplyLocalization(accountManager: AccountManager, postbox: Postbox, network: Network, languageCode: String) -> Signal { return _internal_requestLocalizationPreview(network: network, identifier: languageCode) |> mapError { _ -> DownloadAndApplyLocalizationError in return .generic diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Localization/TelegramEngineLocalization.swift b/submodules/TelegramCore/Sources/TelegramEngine/Localization/TelegramEngineLocalization.swift index 21355fc0a5..f07e4a38d2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Localization/TelegramEngineLocalization.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Localization/TelegramEngineLocalization.swift @@ -9,7 +9,7 @@ public extension TelegramEngine { self.account = account } - public func getCountriesList(accountManager: AccountManager, langCode: String?, forceUpdate: Bool = false) -> Signal<[Country], NoError> { + public func getCountriesList(accountManager: AccountManager, langCode: String?, forceUpdate: Bool = false) -> Signal<[Country], NoError> { return _internal_getCountriesList(accountManager: accountManager, network: self.account.network, langCode: langCode, forceUpdate: forceUpdate) } @@ -29,7 +29,7 @@ public extension TelegramEngine { return _internal_requestLocalizationPreview(network: self.account.network, identifier: identifier) } - public func downloadAndApplyLocalization(accountManager: AccountManager, languageCode: String) -> Signal { + public func downloadAndApplyLocalization(accountManager: AccountManager, languageCode: String) -> Signal { return _internal_downloadAndApplyLocalization(accountManager: accountManager, postbox: self.account.postbox, network: self.account.network, languageCode: languageCode) } } @@ -43,7 +43,7 @@ public extension TelegramEngineUnauthorized { self.account = account } - public func getCountriesList(accountManager: AccountManager, langCode: String?, forceUpdate: Bool = false) -> Signal<[Country], NoError> { + public func getCountriesList(accountManager: AccountManager, langCode: String?, forceUpdate: Bool = false) -> Signal<[Country], NoError> { return _internal_getCountriesList(accountManager: accountManager, network: self.account.network, langCode: langCode, forceUpdate: forceUpdate) } @@ -55,7 +55,7 @@ public extension TelegramEngineUnauthorized { return _internal_currentlySuggestedLocalization(network: self.account.network, extractKeys: extractKeys) } - public func downloadAndApplyLocalization(accountManager: AccountManager, languageCode: String) -> Signal { + public func downloadAndApplyLocalization(accountManager: AccountManager, languageCode: String) -> Signal { return _internal_downloadAndApplyLocalization(accountManager: accountManager, postbox: self.account.postbox, network: self.account.network, languageCode: languageCode) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift new file mode 100644 index 0000000000..d42df61dea --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -0,0 +1,406 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi + +private class AdMessagesHistoryContextImpl { + final class CachedMessage: Equatable, Codable { + enum CodingKeys: String, CodingKey { + case opaqueId + case text + case textEntities + case media + case authorId + } + + public let opaqueId: Data + public let text: String + public let textEntities: [MessageTextEntity] + public let media: [Media] + public let authorId: PeerId + + public init( + opaqueId: Data, + text: String, + textEntities: [MessageTextEntity], + media: [Media], + authorId: PeerId + ) { + self.opaqueId = opaqueId + self.text = text + self.textEntities = textEntities + self.media = media + self.authorId = authorId + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.opaqueId = try container.decode(Data.self, forKey: .opaqueId) + + self.text = try container.decode(String.self, forKey: .text) + self.textEntities = try container.decode([MessageTextEntity].self, forKey: .textEntities) + + let mediaData = try container.decode([Data].self, forKey: .media) + self.media = mediaData.compactMap { data -> Media? in + return PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? Media + } + + self.authorId = try container.decode(PeerId.self, forKey: .authorId) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.opaqueId, forKey: .opaqueId) + try container.encode(self.text, forKey: .text) + try container.encode(self.textEntities, forKey: .textEntities) + + let mediaData = self.media.map { media -> Data in + let encoder = PostboxEncoder() + encoder.encodeRootObject(media) + return encoder.makeData() + } + try container.encode(mediaData, forKey: .media) + + try container.encode(self.authorId, forKey: .authorId) + } + + public static func ==(lhs: CachedMessage, rhs: CachedMessage) -> Bool { + if lhs.opaqueId != rhs.opaqueId { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.textEntities != rhs.textEntities { + return false + } + if lhs.media.count != rhs.media.count { + return false + } + for i in 0 ..< lhs.media.count { + if !lhs.media[i].isEqual(to: rhs.media[i]) { + return false + } + } + if lhs.authorId != rhs.authorId { + return false + } + return true + } + + func toMessage(peerId: PeerId, transaction: Transaction) -> Message { + var attributes: [MessageAttribute] = [] + + attributes.append(AdMessageAttribute(opaqueId: self.opaqueId)) + if !self.textEntities.isEmpty { + let attribute = TextEntitiesMessageAttribute(entities: self.textEntities) + attributes.append(attribute) + } + + var messagePeers = SimpleDictionary() + + if let peer = transaction.getPeer(peerId) { + messagePeers[peer.id] = peer + } + if let peer = transaction.getPeer(self.authorId) { + messagePeers[peer.id] = peer + } + + return Message( + stableId: 0, + stableVersion: 0, + id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 0), + globallyUniqueId: nil, + groupingKey: nil, + groupInfo: nil, + threadId: nil, + timestamp: Int32.max - 1, + flags: [.Incoming], + tags: [], + globalTags: [], + localTags: [], + forwardInfo: nil, + author: transaction.getPeer(self.authorId), + text: self.text, + attributes: attributes, + media: self.media, + peers: messagePeers, + associatedMessages: SimpleDictionary(), + associatedMessageIds: [] + ) + } + } + + private let queue: Queue + private let account: Account + private let peerId: PeerId + + private let maskAsSeenDisposables = DisposableDict() + + struct CachedState: Codable, PostboxCoding { + enum CodingKeys: String, CodingKey { + case timestamp + case messages + } + + var timestamp: Int32 + var messages: [CachedMessage] + + init(timestamp: Int32, messages: [CachedMessage]) { + self.timestamp = timestamp + self.messages = messages + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.timestamp = try container.decode(Int32.self, forKey: .timestamp) + self.messages = try container.decode([CachedMessage].self, forKey: .messages) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.timestamp, forKey: .timestamp) + try container.encode(self.messages, forKey: .messages) + } + + init(decoder: PostboxDecoder) { + self.timestamp = decoder.decodeInt32ForKey("timestamp", orElse: 0) + if let messagesData = decoder.decodeOptionalDataArrayForKey("messages") { + self.messages = messagesData.compactMap { data -> CachedMessage? in + return try? AdaptedPostboxDecoder().decode(CachedMessage.self, from: data) + } + } else { + self.messages = [] + } + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.timestamp, forKey: "timestamp") + encoder.encodeDataArray(self.messages.compactMap { message -> Data? in + return try? AdaptedPostboxEncoder().encode(message) + }, forKey: "messages") + } + + private static let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 5, highWaterItemCount: 10) + + public static func getCached(postbox: Postbox, peerId: PeerId) -> Signal { + return postbox.transaction { transaction -> CachedState? in + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: peerId.toInt64()) + if let entry = transaction.retrieveItemCacheEntryData(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedAdMessageStates, key: key)) { + return try? AdaptedPostboxDecoder().decode(CachedState.self, from: entry) + } else { + return nil + } + } + } + + public static func setCached(transaction: Transaction, peerId: PeerId, state: CachedState?) { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: peerId.toInt64()) + let id = ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedAdMessageStates, key: key) + if let state = state, let stateData = try? AdaptedPostboxEncoder().encode(state) { + transaction.putItemCacheEntryData(id: id, entry: stateData, collectionSpec: collectionSpec) + } else { + transaction.removeItemCacheEntry(id: id) + } + } + } + + struct State: Equatable { + var messages: [Message] + + static func ==(lhs: State, rhs: State) -> Bool { + if lhs.messages.count != rhs.messages.count { + return false + } + for i in 0 ..< lhs.messages.count { + if lhs.messages[i].id != rhs.messages[i].id { + return false + } + if lhs.messages[i].stableId != rhs.messages[i].stableId { + return false + } + } + return true + } + } + + let state = Promise() + private var stateValue: State? { + didSet { + if let stateValue = self.stateValue, stateValue != oldValue { + self.state.set(.single(stateValue)) + } + } + } + + private let disposable = MetaDisposable() + + init(queue: Queue, account: Account, peerId: PeerId) { + self.queue = queue + self.account = account + self.peerId = peerId + + self.stateValue = State(messages: []) + + self.state.set(CachedState.getCached(postbox: account.postbox, peerId: peerId) + |> mapToSignal { cachedState -> Signal in + if let cachedState = cachedState, cachedState.timestamp >= Int32(Date().timeIntervalSince1970) - 5 * 60 { + return account.postbox.transaction { transaction -> State in + return State(messages: cachedState.messages.map { message in + return message.toMessage(peerId: peerId, transaction: transaction) + }) + } + } else { + return .single(State(messages: [])) + } + }) + + let signal: Signal<[Message], NoError> = account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> mapToSignal { inputChannel -> Signal<[Message], NoError> in + guard let inputChannel = inputChannel else { + return .single([]) + } + return account.network.request(Api.functions.channels.getSponsoredMessages(channel: inputChannel)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal<[Message], NoError> in + guard let result = result else { + return .single([]) + } + + return account.postbox.transaction { transaction -> [Message] in + switch result { + case let .sponsoredMessages(messages, chats, users): + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + + updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) + + var parsedMessages: [CachedMessage] = [] + + for message in messages { + switch message { + case let .sponsoredMessage(_, randomId, _, fromId, message, media, entities): + var parsedEntities: [MessageTextEntity] = [] + if let entities = entities { + parsedEntities = messageTextEntitiesFromApiEntities(entities) + } + + var parsedMedia: [Media] = [] + if let media = media { + let (mediaValue, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId) + if let mediaValue = mediaValue { + parsedMedia.append(mediaValue) + } + } + + parsedMessages.append(CachedMessage( + opaqueId: randomId.makeData(), + text: message, + textEntities: parsedEntities, + media: parsedMedia, authorId: fromId.peerId + )) + } + } + + CachedState.setCached(transaction: transaction, peerId: peerId, state: CachedState(timestamp: Int32(Date().timeIntervalSince1970), messages: parsedMessages)) + + return parsedMessages.map { message in + return message.toMessage(peerId: peerId, transaction: transaction) + } + } + } + } + } + + self.disposable.set((signal + |> deliverOn(self.queue)).start(next: { [weak self] messages in + guard let strongSelf = self else { + return + } + strongSelf.stateValue = State(messages: messages) + })) + } + + deinit { + self.disposable.dispose() + self.maskAsSeenDisposables.dispose() + } + + func markAsSeen(opaqueId: Data) { + let signal: Signal = account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(self.peerId).flatMap(apiInputChannel) + } + |> mapToSignal { inputChannel -> Signal in + guard let inputChannel = inputChannel else { + return .complete() + } + return self.account.network.request(Api.functions.channels.viewSponsoredMessage(channel: inputChannel, randomId: Buffer(data: opaqueId))) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> ignoreValues + } + self.maskAsSeenDisposables.set(signal.start(), forKey: opaqueId) + } +} + +public class AdMessagesHistoryContext { + private let queue = Queue() + private let impl: QueueLocalObject + + public var state: Signal<[Message], NoError> { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + let stateDisposable = impl.state.get().start(next: { state in + subscriber.putNext(state.messages) + }) + disposable.set(stateDisposable) + } + + return disposable + } + } + + public init(account: Account, peerId: PeerId) { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return AdMessagesHistoryContextImpl(queue: queue, account: account, peerId: peerId) + }) + } + + public func markAsSeen(opaqueId: Data) { + self.impl.with { impl in + impl.markAsSeen(opaqueId: opaqueId) + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index 3010d19a6d..3f00d8b036 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -4,12 +4,13 @@ import SwiftSignalKit import TelegramApi private struct DiscussionMessage { - public var messageId: MessageId - public var channelMessageId: MessageId? - public var isChannelPost: Bool - public var maxMessage: MessageId? - public var maxReadIncomingMessageId: MessageId? - public var maxReadOutgoingMessageId: MessageId? + var messageId: MessageId + var channelMessageId: MessageId? + var isChannelPost: Bool + var maxMessage: MessageId? + var maxReadIncomingMessageId: MessageId? + var maxReadOutgoingMessageId: MessageId? + var unreadCount: Int } private class ReplyThreadHistoryContextImpl { @@ -45,6 +46,17 @@ private class ReplyThreadHistoryContextImpl { } } } + + private var maxReadIncomingMessageIdValue: MessageId? + + let unreadCount = Promise() + private var unreadCountValue: Int = 0 { + didSet { + if self.unreadCountValue != oldValue { + self.unreadCount.set(.single(self.unreadCountValue)) + } + } + } private var initialStateDisposable: Disposable? private var holesDisposable: Disposable? @@ -59,6 +71,11 @@ private class ReplyThreadHistoryContextImpl { self.maxReadOutgoingMessageIdValue = data.maxReadOutgoingMessageId self.maxReadOutgoingMessageId.set(.single(self.maxReadOutgoingMessageIdValue)) + + self.maxReadIncomingMessageIdValue = data.maxReadIncomingMessageId + + self.unreadCountValue = data.unreadCount + self.unreadCount.set(.single(self.unreadCountValue)) self.initialStateDisposable = (account.postbox.transaction { transaction -> State in var indices = transaction.getThreadIndexHoles(peerId: data.messageId.peerId, threadId: makeMessageThreadId(data.messageId), namespace: Namespaces.Message.Cloud) @@ -134,7 +151,7 @@ private class ReplyThreadHistoryContextImpl { |> mapToSignal { discussionMessage -> Signal in return account.postbox.transaction { transaction -> Signal in switch discussionMessage { - case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, chats, users): + case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, unreadCount, chats, users): let parsedMessages = messages.compactMap { message -> StoreMessage? in StoreMessage(apiMessage: message) } @@ -233,7 +250,8 @@ private class ReplyThreadHistoryContextImpl { maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: readOutboxMaxId.flatMap { readMaxId in MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) - } + }, + unreadCount: Int(unreadCount) )) } } @@ -294,14 +312,27 @@ private class ReplyThreadHistoryContextImpl { } func applyMaxReadIndex(messageIndex: MessageIndex) { - let account = self.account let messageId = self.messageId if messageIndex.id.namespace != messageId.namespace { return } + + guard let _ = self.stateValue else { + return + } + + let fromId: Int32? + let toIndex = messageIndex + if let maxReadIncomingMessageId = self.maxReadIncomingMessageIdValue { + fromId = maxReadIncomingMessageId.id + 1 + } else { + fromId = nil + } + + let account = self.account - let signal = self.account.postbox.transaction { transaction -> Api.InputPeer? in + let _ = (self.account.postbox.transaction { transaction -> (Api.InputPeer?, MessageId?, Int?) in if let message = transaction.getMessage(messageId) { for attribute in message.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { @@ -337,20 +368,84 @@ private class ReplyThreadHistoryContextImpl { } } } + + let inputPeer = transaction.getPeer(messageIndex.id.peerId).flatMap(apiInputPeer) + let readCount = transaction.getThreadMessageCount(peerId: messageId.peerId, threadId: makeMessageThreadId(messageId), namespace: messageId.namespace, fromId: fromId, toIndex: toIndex) + let topMessageId = transaction.getMessagesWithThreadId(peerId: messageId.peerId, namespace: messageId.namespace, threadId: makeMessageThreadId(messageId), from: MessageIndex.upperBound(peerId: messageId.peerId, namespace: messageId.namespace), includeFrom: false, to: MessageIndex.lowerBound(peerId: messageId.peerId, namespace: messageId.namespace), limit: 1).first?.id - return transaction.getPeer(messageIndex.id.peerId).flatMap(apiInputPeer) + return (inputPeer, topMessageId, readCount) } - |> mapToSignal { inputPeer -> Signal in - guard let inputPeer = inputPeer else { - return .complete() + |> deliverOnMainQueue).start(next: { [weak self] inputPeer, topMessageId, readCount in + guard let strongSelf = self else { + return } - return account.network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: messageId.id, readMaxId: messageIndex.id.id)) + + guard let inputPeer = inputPeer else { + return + } + + var revalidate = false + + strongSelf.maxReadIncomingMessageIdValue = messageIndex.id + var unreadCountValue = strongSelf.unreadCountValue + if let readCount = readCount { + unreadCountValue = max(0, unreadCountValue - Int(readCount)) + } else { + revalidate = true + } + + if let topMessageId = topMessageId { + if topMessageId.id <= messageIndex.id.id { + unreadCountValue = 0 + } + } + + strongSelf.unreadCountValue = unreadCountValue + + if let state = strongSelf.stateValue { + if let indices = state.holeIndices[messageIndex.id.namespace] { + let fromIdInt = Int(fromId ?? 1) + let toIdInt = Int(toIndex.id.id) + if fromIdInt <= toIdInt, indices.intersects(integersIn: fromIdInt ..< toIdInt) { + revalidate = true + } + } + } + + var signal = strongSelf.account.network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: messageId.id, readMaxId: messageIndex.id.id)) |> `catch` { _ -> Signal in return .single(.boolFalse) } |> ignoreValues - } - self.readDisposable.set(signal.start()) + if revalidate { + let validateSignal = strongSelf.account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id)) + |> map { result -> (MessageId?, Int) in + switch result { + case let .discussionMessage(_, _, _, readInboxMaxId, _, unreadCount, _, _): + return (readInboxMaxId.flatMap({ MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: $0) }), Int(unreadCount)) + } + } + |> `catch` { _ -> Signal<(MessageId?, Int)?, NoError> in + return .single(nil) + } + |> afterNext { result in + guard let (incomingMesageId, count) = result else { + return + } + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + strongSelf.maxReadIncomingMessageIdValue = incomingMesageId + strongSelf.unreadCountValue = count + } + } + |> ignoreValues + signal = signal + |> then(validateSignal) + } + strongSelf.readDisposable.set(signal.start()) + }) } } @@ -404,6 +499,20 @@ public class ReplyThreadHistoryContext { return disposable } } + + public var unreadCount: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + disposable.set(impl.unreadCount.get().start(next: { value in + subscriber.putNext(value) + })) + } + + return disposable + } + } public init(account: Account, peerId: PeerId, data: ChatReplyThreadMessage) { let queue = self.queue @@ -431,17 +540,19 @@ public struct ChatReplyThreadMessage: Equatable { public var maxMessage: MessageId? public var maxReadIncomingMessageId: MessageId? public var maxReadOutgoingMessageId: MessageId? + public var unreadCount: Int public var initialFilledHoles: IndexSet public var initialAnchor: Anchor public var isNotAvailable: Bool - fileprivate init(messageId: MessageId, channelMessageId: MessageId?, isChannelPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) { + fileprivate init(messageId: MessageId, channelMessageId: MessageId?, isChannelPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, unreadCount: Int, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) { self.messageId = messageId self.channelMessageId = channelMessageId self.isChannelPost = isChannelPost self.maxMessage = maxMessage self.maxReadIncomingMessageId = maxReadIncomingMessageId self.maxReadOutgoingMessageId = maxReadOutgoingMessageId + self.unreadCount = unreadCount self.initialFilledHoles = initialFilledHoles self.initialAnchor = initialAnchor self.isNotAvailable = isNotAvailable @@ -463,7 +574,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa } let replyInfo = Promise() - replyInfo.set(account.viewTracker.replyInfoForMessageId(messageId)) + replyInfo.set(.single(nil)) let remoteDiscussionMessageSignal: Signal = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id)) |> map(Optional.init) @@ -476,7 +587,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa } return account.postbox.transaction { transaction -> DiscussionMessage? in switch discussionMessage { - case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, chats, users): + case let .discussionMessage(_, messages, maxId, readInboxMaxId, readOutboxMaxId, unreadCount, chats, users): let parsedMessages = messages.compactMap { message -> StoreMessage? in StoreMessage(apiMessage: message) } @@ -546,7 +657,8 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa }, maxReadOutgoingMessageId: readOutboxMaxId.flatMap { readMaxId in MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) - } + }, + unreadCount: Int(unreadCount) ) } } @@ -583,7 +695,8 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa isChannelPost: true, maxMessage: replyInfo.maxMessageId, maxReadIncomingMessageId: replyInfo.maxReadIncomingMessageId, - maxReadOutgoingMessageId: nil + maxReadOutgoingMessageId: nil, + unreadCount: 0 ) } }) @@ -785,6 +898,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa maxMessage: discussionMessage.maxMessage, maxReadIncomingMessageId: discussionMessage.maxReadIncomingMessageId, maxReadOutgoingMessageId: discussionMessage.maxReadOutgoingMessageId, + unreadCount: discussionMessage.unreadCount, initialFilledHoles: initialFilledHoles?.removedIndices ?? IndexSet(), initialAnchor: initialAnchor, isNotAvailable: initialFilledHoles == nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 6cda567415..4dd7be6908 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -199,5 +199,9 @@ public extension TelegramEngine { ) } } + + public func adMessages(peerId: PeerId) -> AdMessagesHistoryContext { + return AdMessagesHistoryContext(account: self.account, peerId: peerId) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index 90a5c24bd5..30eff8ab1e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -860,6 +860,11 @@ func _internal_currentChatListFilters(postbox: Postbox) -> Signal<[ChatListFilte } } +func _internal_currentChatListFilters(transaction: Transaction) -> [ChatListFilter] { + let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default + return settings.filters +} + func updateChatListFiltersState(transaction: Transaction, _ f: (ChatListFiltersState) -> ChatListFiltersState) -> ChatListFiltersState { var result: ChatListFiltersState? transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index d4edba3fa9..9d22f9f84b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -25,6 +25,13 @@ public final class OpaqueChatInterfaceState { } public extension TelegramEngine { + enum NextUnreadChannelLocation: Equatable { + case same + case archived + case unarchived + case folder(id: Int32, title: String) + } + final class Peers { private let account: Account @@ -495,37 +502,92 @@ public extension TelegramEngine { return _internal_updatePeerDescription(account: self.account, peerId: peerId, description: description) } - public func getNextUnreadChannel(peerId: PeerId, filter: ChatListFilterPredicate?) -> Signal { - return self.account.postbox.transaction { transaction -> EnginePeer? in - var results: [(EnginePeer, Int32)] = [] - - var peerIds: [PeerId] = [] - peerIds.append(contentsOf: transaction.getUnreadChatListPeerIds(groupId: .root, filterPredicate: filter)) - peerIds.append(contentsOf: transaction.getUnreadChatListPeerIds(groupId: Namespaces.PeerGroup.archive, filterPredicate: filter)) - - for listId in peerIds { - guard let peer = transaction.getPeer(listId) else { - continue - } - guard let channel = peer as? TelegramChannel, case .broadcast = channel.info else { - continue - } - if channel.id == peerId { - continue - } - guard let readState = transaction.getCombinedPeerReadState(channel.id), readState.count != 0 else { - continue - } - guard let topMessageIndex = transaction.getTopPeerMessageIndex(peerId: channel.id) else { - continue + public func getNextUnreadChannel(peerId: PeerId, chatListFilterId: Int32?, getFilterPredicate: @escaping (ChatListFilterData) -> ChatListFilterPredicate) -> Signal<(peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)?, NoError> { + return self.account.postbox.transaction { transaction -> (peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)? in + func getForFilter(predicate: ChatListFilterPredicate?, isArchived: Bool) -> (peer: EnginePeer, unreadCount: Int)? { + var peerIds: [PeerId] = [] + if predicate != nil { + peerIds.append(contentsOf: transaction.getUnreadChatListPeerIds(groupId: .root, filterPredicate: predicate)) + peerIds.append(contentsOf: transaction.getUnreadChatListPeerIds(groupId: Namespaces.PeerGroup.archive, filterPredicate: predicate)) + } else { + if isArchived { + peerIds.append(contentsOf: transaction.getUnreadChatListPeerIds(groupId: Namespaces.PeerGroup.archive, filterPredicate: nil)) + } else { + peerIds.append(contentsOf: transaction.getUnreadChatListPeerIds(groupId: .root, filterPredicate: nil)) + } } - results.append((EnginePeer(channel), topMessageIndex.timestamp)) + var results: [(EnginePeer, Int32)] = [] + + for listId in peerIds { + guard let peer = transaction.getPeer(listId) else { + continue + } + guard let channel = peer as? TelegramChannel, case .broadcast = channel.info else { + continue + } + if channel.id == peerId { + continue + } + guard let readState = transaction.getCombinedPeerReadState(channel.id), readState.count != 0 else { + continue + } + guard let topMessageIndex = transaction.getTopPeerMessageIndex(peerId: channel.id) else { + continue + } + + results.append((EnginePeer(channel), topMessageIndex.timestamp)) + } + + results.sort(by: { $0.1 > $1.1 }) + + if let peer = results.first?.0 { + let unreadCount: Int32 = transaction.getCombinedPeerReadState(peer.id)?.count ?? 0 + return (peer: peer, unreadCount: Int(unreadCount)) + } else { + return nil + } } - results.sort(by: { $0.1 > $1.1 }) + guard let peerGroupId = transaction.getPeerChatListIndex(peerId)?.0 else { + return nil + } - return results.first?.0 + if let filterId = chatListFilterId { + let filters = _internal_currentChatListFilters(transaction: transaction) + guard let index = filters.firstIndex(where: { $0.id == filterId }) else { + return nil + } + var sortedFilters: [ChatListFilter] = [] + sortedFilters.append(contentsOf: filters[index...]) + sortedFilters.append(contentsOf: filters[0 ..< index]) + for i in 0 ..< sortedFilters.count { + if let value = getForFilter(predicate: getFilterPredicate(sortedFilters[i].data), isArchived: false) { + return (peer: value.peer, unreadCount: value.unreadCount, location: i == 0 ? .same : .folder(id: sortedFilters[i].id, title: sortedFilters[i].title)) + } + } + return nil + } else { + let folderOrder: [(PeerGroupId, NextUnreadChannelLocation)] + if peerGroupId == .root { + folderOrder = [ + (.root, .same), + (Namespaces.PeerGroup.archive, .archived), + ] + } else { + folderOrder = [ + (Namespaces.PeerGroup.archive, .same), + (.root, .unarchived), + ] + } + + for (groupId, location) in folderOrder { + if let value = getForFilter(predicate: nil, isArchived: groupId != .root) { + return (peer: value.peer, unreadCount: value.unreadCount, location: location) + } + } + return nil + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 06f8b48e9e..a2bfc8fc3c 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 @@ -357,7 +359,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } switch fullChat { - case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs): + case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon): var channelFlags = CachedChannelFlags() if (flags & (1 << 3)) != 0 { channelFlags.insert(.canDisplayParticipants) @@ -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/Stickers/SearchStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift index 0eed25d054..34a3670355 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift @@ -3,7 +3,6 @@ import Postbox import TelegramApi import SwiftSignalKit - private struct SearchStickersConfiguration { static var defaultValue: SearchStickersConfiguration { return SearchStickersConfiguration(cacheTimeout: 86400) @@ -16,8 +15,8 @@ private struct SearchStickersConfiguration { } static func with(appConfiguration: AppConfiguration) -> SearchStickersConfiguration { - if let data = appConfiguration.data, let value = data["stickers_emoji_cache_time"] as? Int32 { - return SearchStickersConfiguration(cacheTimeout: value) + if let data = appConfiguration.data, let value = data["stickers_emoji_cache_time"] as? Double { + return SearchStickersConfiguration(cacheTimeout: Int32(value)) } else { return .defaultValue } 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 060233a14f..5c6ab6d937 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" @@ -12,7 +11,7 @@ let telegramThemeFormat = "ios" let telegramThemeFileExtension = "tgios-theme" #endif -public func telegramThemes(postbox: Postbox, network: Network, accountManager: AccountManager?, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> { +public func telegramThemes(postbox: Postbox, network: Network, accountManager: AccountManager?, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> { let fetch: ([TelegramTheme]?, Int64?) -> Signal<[TelegramTheme], NoError> = { current, hash in network.request(Api.functions.account.getThemes(format: telegramThemeFormat, hash: hash ?? 0)) |> retryRequest @@ -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,15 +117,11 @@ 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)) } } -private func saveUnsaveTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme, unsave: Bool) -> Signal { +private func saveUnsaveTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme, unsave: Bool) -> Signal { return account.postbox.transaction { transaction -> Signal in let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) var items = entries.map { $0.contents as! TelegramTheme } @@ -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,29 +336,28 @@ 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) - } - |> castError(CreateThemeError.self) - } else { - return .fail(.generic) + 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) } } } -public func updateTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal { +public func updateTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme, title: String?, slug: String?, resource: MediaResource?, thumbnailData: Data? = nil, settings: TelegramThemeSettings?) -> Signal { guard title != nil || slug != nil || resource != nil else { return .complete() } @@ -427,52 +414,51 @@ public func updateTheme(account: Account, accountManager: AccountManager, theme: return .generic } |> mapToSignal { apiTheme -> Signal in - if let updatedTheme = TelegramTheme(apiTheme: apiTheme) { - let _ = accountManager.transaction { transaction in - transaction.updateSharedData(SharedDataKeys.themeSettings, { current in - var updated = current as? ThemeSettings ?? ThemeSettings(currentTheme: nil) - if updatedTheme.id == updated.currentTheme?.id { - updated = ThemeSettings(currentTheme: updatedTheme) - } - return updated - }) - }.start() - return account.postbox.transaction { transaction -> CreateThemeResult in - let entries = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudThemes) - let items = entries.map { entry -> TelegramTheme in - let theme = entry.contents as! TelegramTheme - if theme.id == updatedTheme.id { - return updatedTheme - } else { - return theme - } + 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) } - 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) } } } -public func saveThemeInteractively(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { +public func saveThemeInteractively(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { return saveUnsaveTheme(account: account, accountManager: accountManager, theme: theme, unsave: false) } -public func deleteThemeInteractively(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { +public func deleteThemeInteractively(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { return saveUnsaveTheme(account: account, accountManager: accountManager, theme: theme, unsave: true) } -public func applyTheme(accountManager: AccountManager, account: Account, theme: TelegramTheme?, autoNight: Bool = false) -> Signal { +public func applyTheme(accountManager: AccountManager, account: Account, theme: TelegramTheme?, autoNight: Bool = false) -> Signal { return accountManager.transaction { transaction -> Signal in transaction.updateSharedData(SharedDataKeys.themeSettings, { _ in return ThemeSettings(currentTheme: theme) @@ -487,7 +473,7 @@ public func applyTheme(accountManager: AccountManager, account: Account, theme: |> switchToLatest } -func managedThemesUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { +func managedThemesUpdates(accountManager: AccountManager, postbox: Postbox, network: Network) -> Signal { let currentTheme = Atomic(value: nil) return accountManager.sharedData(keys: [SharedDataKeys.themeSettings]) |> map { sharedData -> TelegramTheme? in @@ -555,7 +541,7 @@ private func areThemesEqual(_ lhs: TelegramTheme, _ rhs: TelegramTheme) -> Bool return true } -public func actualizedTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { +public func actualizedTheme(account: Account, accountManager: AccountManager, theme: TelegramTheme) -> Signal { var currentTheme = theme return accountManager.sharedData(keys: [SharedDataKeys.themeSettings]) |> mapToSignal { sharedData -> Signal in diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index d0ce58753c..0582f4bb05 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -291,3 +291,14 @@ public extension Message { } } +public extension Message { + var adAttribute: AdMessageAttribute? { + for attribute in self.attributes { + if let attribute = attribute as? AdMessageAttribute { + return attribute + } + } + return nil + } +} + diff --git a/submodules/TelegramNotices/BUILD b/submodules/TelegramNotices/BUILD index 351f442adf..5a8b8127a4 100644 --- a/submodules/TelegramNotices/BUILD +++ b/submodules/TelegramNotices/BUILD @@ -11,6 +11,7 @@ swift_library( ], deps = [ "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/TelegramPermissions:TelegramPermissions", ], diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 38957258e6..f5d7855046 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit import TelegramPermissions @@ -103,6 +104,33 @@ public final class ApplicationSpecificTimestampNotice: NoticeEntry { } } +public final class ApplicationSpecificInt64ArrayNotice: NoticeEntry { + public let values: [Int64] + + public init(values: [Int64]) { + self.values = values + } + + public init(decoder: PostboxDecoder) { + self.values = decoder.decodeInt64ArrayForKey("v") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64Array(self.values, forKey: "v") + } + + public func isEqual(to: NoticeEntry) -> Bool { + if let to = to as? ApplicationSpecificInt64ArrayNotice { + if self.values != to.values { + return false + } + return true + } else { + return false + } + } +} + private func noticeNamespace(namespace: Int32) -> ValueBoxKey { let key = ValueBoxKey(length: 4) key.setInt32(0, value: namespace) @@ -138,6 +166,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case chatFolderTips = 19 case locationProximityAlertTip = 20 case nextChatSuggestionTip = 21 + case dismissedTrendingStickerPacks = 22 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -273,6 +302,10 @@ private struct ApplicationSpecificNoticeKeys { static func nextChatSuggestionTip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.nextChatSuggestionTip.key) } + + static func dismissedTrendingStickerPacks() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingStickerPacks.key) + } } public struct ApplicationSpecificNotice { @@ -286,7 +319,7 @@ public struct ApplicationSpecificNotice { } } - public static func getBotPaymentLiability(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func getBotPaymentLiability(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.botPaymentLiabilityNotice(peerId: peerId)) as? ApplicationSpecificBoolNotice { return true @@ -296,13 +329,13 @@ public struct ApplicationSpecificNotice { } } - public static func setBotPaymentLiability(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func setBotPaymentLiability(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.botPaymentLiabilityNotice(peerId: peerId), ApplicationSpecificBoolNotice()) } } - public static func getBotGameNotice(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func getBotGameNotice(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.botGameNotice(peerId: peerId)) as? ApplicationSpecificBoolNotice { return true @@ -312,13 +345,13 @@ public struct ApplicationSpecificNotice { } } - public static func setBotGameNotice(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func setBotGameNotice(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.botGameNotice(peerId: peerId), ApplicationSpecificBoolNotice()) } } - public static func getInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func getInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.transaction { transaction -> Int32? in if let notice = transaction.getNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId)) as? ApplicationSpecificTimestampNotice { return notice.value @@ -328,7 +361,7 @@ public struct ApplicationSpecificNotice { } } - public static func inlineBotLocationRequestStatus(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func inlineBotLocationRequestStatus(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId)) |> map { view -> Bool in guard let value = view.value as? ApplicationSpecificTimestampNotice else { @@ -342,7 +375,7 @@ public struct ApplicationSpecificNotice { } } - public static func updateInlineBotLocationRequestState(accountManager: AccountManager, peerId: PeerId, timestamp: Int32) -> Signal { + public static func updateInlineBotLocationRequestState(accountManager: AccountManager, peerId: PeerId, timestamp: Int32) -> Signal { return accountManager.transaction { transaction -> Bool in if let notice = transaction.getNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId)) as? ApplicationSpecificTimestampNotice, (notice.value == 0 || timestamp <= notice.value + 10 * 60) { return false @@ -354,13 +387,13 @@ public struct ApplicationSpecificNotice { } } - public static func setInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId, value: Int32) -> Signal { + public static func setInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId, value: Int32) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId), ApplicationSpecificTimestampNotice(value: value)) } } - public static func getSecretChatInlineBotUsage(accountManager: AccountManager) -> Signal { + public static func getSecretChatInlineBotUsage(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.secretChatInlineBotUsage()) as? ApplicationSpecificBoolNotice { return true @@ -370,17 +403,17 @@ public struct ApplicationSpecificNotice { } } - public static func setSecretChatInlineBotUsage(accountManager: AccountManager) -> Signal { + public static func setSecretChatInlineBotUsage(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.secretChatInlineBotUsage(), ApplicationSpecificBoolNotice()) } } - public static func setSecretChatInlineBotUsage(transaction: AccountManagerModifier) { + public static func setSecretChatInlineBotUsage(transaction: AccountManagerModifier) { transaction.setNotice(ApplicationSpecificNoticeKeys.secretChatInlineBotUsage(), ApplicationSpecificBoolNotice()) } - public static func getSecretChatLinkPreviews(accountManager: AccountManager) -> Signal { + public static func getSecretChatLinkPreviews(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Bool? in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.secretChatLinkPreviews()) as? ApplicationSpecificVariantNotice { return value.value @@ -398,13 +431,13 @@ public struct ApplicationSpecificNotice { } } - public static func setSecretChatLinkPreviews(accountManager: AccountManager, value: Bool) -> Signal { + public static func setSecretChatLinkPreviews(accountManager: AccountManager, value: Bool) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.secretChatLinkPreviews(), ApplicationSpecificVariantNotice(value: value)) } } - public static func setSecretChatLinkPreviews(transaction: AccountManagerModifier, value: Bool) { + public static func setSecretChatLinkPreviews(transaction: AccountManagerModifier, value: Bool) { transaction.setNotice(ApplicationSpecificNoticeKeys.secretChatLinkPreviews(), ApplicationSpecificVariantNotice(value: value)) } @@ -412,7 +445,7 @@ public struct ApplicationSpecificNotice { return ApplicationSpecificNoticeKeys.secretChatLinkPreviews() } - public static func getChatMediaMediaRecordingTips(accountManager: AccountManager) -> Signal { + public static func getChatMediaMediaRecordingTips(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatMediaMediaRecordingTips()) as? ApplicationSpecificCounterNotice { return value.value @@ -422,7 +455,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementChatMediaMediaRecordingTips(accountManager: AccountManager, count: Int32 = 1) -> Signal { + public static func incrementChatMediaMediaRecordingTips(accountManager: AccountManager, count: Int32 = 1) -> Signal { return accountManager.transaction { transaction -> Void in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatMediaMediaRecordingTips()) as? ApplicationSpecificCounterNotice { @@ -434,7 +467,7 @@ public struct ApplicationSpecificNotice { } } - public static func getArchiveChatTips(accountManager: AccountManager) -> Signal { + public static func getArchiveChatTips(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.archiveChatTips()) as? ApplicationSpecificCounterNotice { return value.value @@ -444,7 +477,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementArchiveChatTips(accountManager: AccountManager, count: Int = 1) -> Signal { + public static func incrementArchiveChatTips(accountManager: AccountManager, count: Int = 1) -> Signal { return accountManager.transaction { transaction -> Int in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.archiveChatTips()) as? ApplicationSpecificCounterNotice { @@ -459,7 +492,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementChatFolderTips(accountManager: AccountManager, count: Int = 1) -> Signal { + public static func incrementChatFolderTips(accountManager: AccountManager, count: Int = 1) -> Signal { return accountManager.transaction { transaction -> Int in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatFolderTips()) as? ApplicationSpecificCounterNotice { @@ -474,7 +507,7 @@ public struct ApplicationSpecificNotice { } } - public static func setArchiveIntroDismissed(transaction: AccountManagerModifier, value: Bool) { + public static func setArchiveIntroDismissed(transaction: AccountManagerModifier, value: Bool) { transaction.setNotice(ApplicationSpecificNoticeKeys.archiveIntroDismissed(), ApplicationSpecificVariantNotice(value: value)) } @@ -482,7 +515,7 @@ public struct ApplicationSpecificNotice { return ApplicationSpecificNoticeKeys.archiveIntroDismissed() } - public static func getProfileCallTips(accountManager: AccountManager) -> Signal { + public static func getProfileCallTips(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.profileCallTips()) as? ApplicationSpecificCounterNotice { return value.value @@ -492,7 +525,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementProfileCallTips(accountManager: AccountManager, count: Int32 = 1) -> Signal { + public static func incrementProfileCallTips(accountManager: AccountManager, count: Int32 = 1) -> Signal { return accountManager.transaction { transaction -> Void in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.profileCallTips()) as? ApplicationSpecificCounterNotice { @@ -504,7 +537,7 @@ public struct ApplicationSpecificNotice { } } - public static func getSetPublicChannelLink(accountManager: AccountManager) -> Signal { + public static func getSetPublicChannelLink(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Bool in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.profileCallTips()) as? ApplicationSpecificCounterNotice { return value.value < 1 @@ -514,13 +547,13 @@ public struct ApplicationSpecificNotice { } } - public static func markAsSeenSetPublicChannelLink(accountManager: AccountManager) -> Signal { + public static func markAsSeenSetPublicChannelLink(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.profileCallTips(), ApplicationSpecificCounterNotice(value: 1)) } } - public static func getProxyAdsAcknowledgment(accountManager: AccountManager) -> Signal { + public static func getProxyAdsAcknowledgment(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.proxyAdsAcknowledgment()) as? ApplicationSpecificBoolNotice { return true @@ -530,13 +563,13 @@ public struct ApplicationSpecificNotice { } } - public static func setProxyAdsAcknowledgment(accountManager: AccountManager) -> Signal { + public static func setProxyAdsAcknowledgment(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.proxyAdsAcknowledgment(), ApplicationSpecificBoolNotice()) } } - public static func getPsaAcknowledgment(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func getPsaAcknowledgment(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.psaAdsAcknowledgment(peerId: peerId)) as? ApplicationSpecificBoolNotice { return true @@ -546,13 +579,13 @@ public struct ApplicationSpecificNotice { } } - public static func setPsaAcknowledgment(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func setPsaAcknowledgment(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.psaAdsAcknowledgment(peerId: peerId), ApplicationSpecificBoolNotice()) } } - public static func getPasscodeLockTips(accountManager: AccountManager) -> Signal { + public static func getPasscodeLockTips(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.passcodeLockTips()) as? ApplicationSpecificBoolNotice { return true @@ -562,7 +595,7 @@ public struct ApplicationSpecificNotice { } } - public static func setPasscodeLockTips(accountManager: AccountManager) -> Signal { + public static func setPasscodeLockTips(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.passcodeLockTips(), ApplicationSpecificBoolNotice()) } @@ -572,7 +605,7 @@ public struct ApplicationSpecificNotice { return permission.noticeKey } - public static func setPermissionWarning(accountManager: AccountManager, permission: PermissionKind, value: Int32) { + public static func setPermissionWarning(accountManager: AccountManager, permission: PermissionKind, value: Int32) { guard let noticeKey = permission.noticeKey else { return } @@ -589,7 +622,7 @@ public struct ApplicationSpecificNotice { } } - public static func getVolumeButtonToUnmute(accountManager: AccountManager) -> Signal { + public static func getVolumeButtonToUnmute(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.volumeButtonToUnmuteTip()) as? ApplicationSpecificBoolNotice { return true @@ -599,13 +632,13 @@ public struct ApplicationSpecificNotice { } } - public static func setVolumeButtonToUnmute(accountManager: AccountManager) { + public static func setVolumeButtonToUnmute(accountManager: AccountManager) { let _ = accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.volumeButtonToUnmuteTip(), ApplicationSpecificBoolNotice()) }.start() } - public static func getCallsTabTip(accountManager: AccountManager) -> Signal { + public static func getCallsTabTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.callsTabTip()) as? ApplicationSpecificCounterNotice { return value.value @@ -615,7 +648,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementCallsTabTips(accountManager: AccountManager, count: Int = 1) -> Signal { + public static func incrementCallsTabTips(accountManager: AccountManager, count: Int = 1) -> Signal { return accountManager.transaction { transaction -> Int in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.callsTabTip()) as? ApplicationSpecificCounterNotice { @@ -630,14 +663,14 @@ public struct ApplicationSpecificNotice { } } - public static func setCallsTabTip(accountManager: AccountManager) -> Signal { + public static func setCallsTabTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.callsTabTip(), ApplicationSpecificBoolNotice()) } } - public static func getChatMessageSearchResultsTip(accountManager: AccountManager) -> Signal { + public static func getChatMessageSearchResultsTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatMessageSearchResultsTip()) as? ApplicationSpecificCounterNotice { return value.value @@ -647,7 +680,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementChatMessageSearchResultsTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { + public static func incrementChatMessageSearchResultsTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { return accountManager.transaction { transaction -> Void in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatMessageSearchResultsTip()) as? ApplicationSpecificCounterNotice { @@ -659,7 +692,7 @@ public struct ApplicationSpecificNotice { } } - public static func getChatMessageOptionsTip(accountManager: AccountManager) -> Signal { + public static func getChatMessageOptionsTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatMessageOptionsTip()) as? ApplicationSpecificCounterNotice { return value.value @@ -669,7 +702,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementChatMessageOptionsTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { + public static func incrementChatMessageOptionsTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { return accountManager.transaction { transaction -> Void in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatMessageOptionsTip()) as? ApplicationSpecificCounterNotice { @@ -681,7 +714,7 @@ public struct ApplicationSpecificNotice { } } - public static func getChatTextSelectionTips(accountManager: AccountManager) -> Signal { + public static func getChatTextSelectionTips(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatTextSelectionTip()) as? ApplicationSpecificCounterNotice { return value.value @@ -691,7 +724,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementChatTextSelectionTips(accountManager: AccountManager, count: Int32 = 1) -> Signal { + public static func incrementChatTextSelectionTips(accountManager: AccountManager, count: Int32 = 1) -> Signal { return accountManager.transaction { transaction -> Void in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatTextSelectionTip()) as? ApplicationSpecificCounterNotice { @@ -703,7 +736,7 @@ public struct ApplicationSpecificNotice { } } - public static func getThemeChangeTip(accountManager: AccountManager) -> Signal { + public static func getThemeChangeTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.themeChangeTip()) as? ApplicationSpecificBoolNotice { return true @@ -713,13 +746,13 @@ public struct ApplicationSpecificNotice { } } - public static func markThemeChangeTipAsSeen(accountManager: AccountManager) { + public static func markThemeChangeTipAsSeen(accountManager: AccountManager) { let _ = accountManager.transaction { transaction -> Void in transaction.setNotice(ApplicationSpecificNoticeKeys.themeChangeTip(), ApplicationSpecificBoolNotice()) }.start() } - public static func getLocationProximityAlertTip(accountManager: AccountManager) -> Signal { + public static func getLocationProximityAlertTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatMessageOptionsTip()) as? ApplicationSpecificCounterNotice { return value.value @@ -729,7 +762,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementLocationProximityAlertTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { + public static func incrementLocationProximityAlertTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { return accountManager.transaction { transaction -> Void in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatMessageOptionsTip()) as? ApplicationSpecificCounterNotice { @@ -741,7 +774,7 @@ public struct ApplicationSpecificNotice { } } - public static func getNextChatSuggestionTip(accountManager: AccountManager) -> Signal { + public static func getNextChatSuggestionTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.nextChatSuggestionTip()) as? ApplicationSpecificCounterNotice { return value.value @@ -751,7 +784,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementNextChatSuggestionTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { + public static func incrementNextChatSuggestionTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { return accountManager.transaction { transaction -> Void in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.nextChatSuggestionTip()) as? ApplicationSpecificCounterNotice { @@ -763,7 +796,24 @@ public struct ApplicationSpecificNotice { } } - public static func reset(accountManager: AccountManager) -> Signal { + public static func dismissedTrendingStickerPacks(accountManager: AccountManager) -> Signal<[Int64]?, NoError> { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks()) + |> map { view -> [Int64]? in + if let value = view.value as? ApplicationSpecificInt64ArrayNotice { + return value.values + } else { + return nil + } + } + } + + public static func setDismissedTrendingStickerPacks(accountManager: AccountManager, values: [Int64]) -> Signal { + return accountManager.transaction { transaction -> Void in + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks(), ApplicationSpecificInt64ArrayNotice(values: values)) + } + } + + public static func reset(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index f1ced61ca5..ada7119ead 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -4,25 +4,9 @@ import TelegramCore import TelegramUIPreferences public let defaultDarkPresentationTheme = makeDefaultDarkPresentationTheme(preview: false) -public let defaultDarkColorPresentationTheme = customizeDefaultDarkPresentationTheme(theme: defaultDarkPresentationTheme, editing: false, title: nil, accentColor: UIColor(rgb: 0x007aff), backgroundColors: [], bubbleColors: nil, wallpaper: nil, baseColor: nil) +public let defaultDarkColorPresentationTheme = customizeDefaultDarkPresentationTheme(theme: defaultDarkPresentationTheme, editing: false, title: nil, accentColor: UIColor(rgb: 0x007aff), backgroundColors: [], bubbleColors: [], animateBubbleColors: false, wallpaper: nil, baseColor: nil) private extension PresentationThemeBaseColor { -/* - - Оранжевая с красным - https://t.me/bg/9LW_RcoOSVACAAAAFTk3DTyXN-M?bg_color=fec496~dd6cb9~962fbf~4f5bd5&intensity=-40 - - Голубая с розовым - https://t.me/bg/9iklpvIPQVABAAAAORQXKur_Eyc?bg_color=8adbf2~888dec~e39fea~679ced&intensity=-30 - - Мятная - https://t.me/bg/CJNyxPMgSVAEAAAAvW9sMwc51cw?bg_color=7fa381~fff5c5~336f55~fbe37d&intensity=-20 - - Синяя с розовым - https://t.me/bg/9LW_RcoOSVACAAAAFTk3DTyXN-M?bg_color=fec496~dd6cb9~962fbf~4f5bd5&intensity=-40 - -*/ - var colorWallpaper: (BuiltinWallpaperData, Int32, [UInt32])? { switch self { case .blue: @@ -53,7 +37,7 @@ private extension PresentationThemeBaseColor { } } -public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: (UIColor, UIColor?)?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme { +public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme { if (theme.referenceTheme != .night) { return theme } @@ -67,13 +51,13 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit var bubbleColors = bubbleColors var monochrome = false - if bubbleColors == nil, editing { + if bubbleColors.isEmpty, editing { let accentColor = accentColor ?? UIColor(rgb: 0xffffff) if accentColor.rgb == 0xffffff { monochrome = true - bubbleColors = (UIColor(rgb: 0x313131), UIColor(rgb: 0x313131)) + bubbleColors = [UIColor(rgb: 0x313131).rgb, UIColor(rgb: 0x313131).rgb] } else { - bubbleColors = (accentColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98), accentColor) + bubbleColors = [accentColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98).rgb, accentColor.rgb] } } @@ -138,17 +122,16 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit } } - var outgoingBubbleFillColor: UIColor? - var outgoingBubbleFillGradientColor: UIColor? + var outgoingBubbleFillColors: [UIColor]? var outgoingPrimaryTextColor: UIColor? var outgoingSecondaryTextColor: UIColor? var outgoingLinkTextColor: UIColor? var outgoingScamColor: UIColor? var outgoingCheckColor: UIColor? - if let bubbleColors = bubbleColors { - var topBubbleColor = bubbleColors.0 - var bottomBubbleColor = bubbleColors.1 ?? bubbleColors.0 + if !bubbleColors.isEmpty { + var topBubbleColor = UIColor(rgb: bubbleColors[0]) + var bottomBubbleColor = UIColor(rgb: bubbleColors.last ?? bubbleColors[0]) if topBubbleColor.rgb != bottomBubbleColor.rgb { let topBubbleColorLightness = topBubbleColor.lightness @@ -162,8 +145,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit } } - outgoingBubbleFillColor = topBubbleColor - outgoingBubbleFillGradientColor = bottomBubbleColor + outgoingBubbleFillColors = bubbleColors.map(UIColor.init(rgb:)) let lightnessColor = topBubbleColor.mixedWith(bottomBubbleColor, alpha: 0.5) if lightnessColor.lightness > 0.7 { @@ -183,6 +165,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit chat = chat.withUpdated( defaultWallpaper: defaultWallpaper, + animateMessageColors: animateBubbleColors, message: chat.message.withUpdated( incoming: chat.message.incoming.withUpdated( linkTextColor: accentColor, @@ -210,15 +193,13 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit outgoing: chat.message.outgoing.withUpdated( bubble: chat.message.outgoing.bubble.withUpdated( withWallpaper: chat.message.outgoing.bubble.withWallpaper.withUpdated( - fill: outgoingBubbleFillColor, - gradientFill: outgoingBubbleFillGradientColor, - highlightedFill: outgoingBubbleFillColor?.withMultipliedBrightnessBy(1.421), + fill: outgoingBubbleFillColors, + highlightedFill: outgoingBubbleFillColors?.first?.withMultipliedBrightnessBy(1.421), stroke: .clear ), withoutWallpaper: chat.message.outgoing.bubble.withoutWallpaper.withUpdated( - fill: outgoingBubbleFillColor, - gradientFill: outgoingBubbleFillGradientColor, - highlightedFill: outgoingBubbleFillColor?.withMultipliedBrightnessBy(1.421), + fill: outgoingBubbleFillColors, + highlightedFill: outgoingBubbleFillColors?.first?.withMultipliedBrightnessBy(1.421), stroke: .clear ) ), @@ -230,7 +211,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, - mediaControlInnerBackgroundColor: outgoingBubbleFillColor, + mediaControlInnerBackgroundColor: outgoingBubbleFillColors?.first, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, @@ -455,9 +436,9 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati let incomingBubbleAlpha: CGFloat = 0.9 let message = PresentationThemeChatMessage( - incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1D1D1D, alpha: incomingBubbleAlpha), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1D1D1D, alpha: incomingBubbleAlpha), highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628), shadow: nil)), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.4), mediaControlInnerBackgroundColor: UIColor(rgb: 0x262628), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x1f1f1f).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x737373), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff, alpha: 0.12), separator: UIColor(rgb: 0x000000), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0x00A700), barNegative: UIColor(rgb: 0xFE3824)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)), - outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x61BCF9), gradientFill: UIColor(rgb: 0x007AFF), highlightedFill: UIColor(rgb: 0x61BCF9), stroke: .clear, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x61BCF9), gradientFill: UIColor(rgb: 0x007AFF), highlightedFill: UIColor(rgb: 0x61BCF9), stroke: .clear, shadow: nil)), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaControlInnerBackgroundColor: UIColor(rgb: 0x313131), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xffffff), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff).withAlphaComponent(0.12), separator: UIColor(rgb: 0xffffff, alpha: 0.5), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0xffffff), barNegative: UIColor(rgb: 0xffffff)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)), - freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f), shadow: nil)), + incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0x1D1D1D, alpha: incomingBubbleAlpha)], highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0x1D1D1D, alpha: incomingBubbleAlpha)], highlightedFill: UIColor(rgb: 0x353539), stroke: UIColor(rgb: 0x262628), shadow: nil)), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.4), mediaControlInnerBackgroundColor: UIColor(rgb: 0x262628), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x1f1f1f).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0x737373), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff, alpha: 0.12), separator: UIColor(rgb: 0x000000), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0x00A700), barNegative: UIColor(rgb: 0xFE3824)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)), + outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x007AFF)], highlightedFill: UIColor(rgb: 0x61BCF9), stroke: .clear, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x007AFF)], highlightedFill: UIColor(rgb: 0x61BCF9), stroke: .clear, shadow: nil)), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaControlInnerBackgroundColor: UIColor(rgb: 0x313131), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xffffff), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff).withAlphaComponent(0.12), separator: UIColor(rgb: 0xffffff, alpha: 0.5), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0xffffff), barNegative: UIColor(rgb: 0xffffff)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)), + freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0x1f1f1f)], highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0x1f1f1f)], highlightedFill: UIColor(rgb: 0x2a2a2a), stroke: UIColor(rgb: 0x1f1f1f), shadow: nil)), infoPrimaryTextColor: UIColor(rgb: 0xffffff), infoLinkTextColor: UIColor(rgb: 0xffffff), outgoingCheckColor: UIColor(rgb: 0xffffff), @@ -543,6 +524,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati let chat = PresentationThemeChat( defaultWallpaper: .color(0x000000), + animateMessageColors: false, message: message, serviceMessage: serviceMessage, inputPanel: inputPanel, diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index 23341096d1..5c6a24eae8 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -38,7 +38,7 @@ private extension PresentationThemeBaseColor { } } -public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: (UIColor, UIColor?)?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme { +public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme { if (theme.referenceTheme != .nightAccent) { return theme } @@ -75,7 +75,7 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme var suggestedWallpaper: TelegramWallpaper? var bubbleColors = bubbleColors - if bubbleColors == nil, editing { + if bubbleColors.isEmpty, editing { if let accentColor = accentColor { if let baseColor = baseColor, let (variant, intensity, colors) = baseColor.colorWallpaper, !colors.isEmpty { suggestedWallpaper = defaultBuiltinWallpaper(data: variant, colors: colors, intensity: intensity) @@ -88,7 +88,7 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme let accentColor = accentColor ?? defaultDarkTintedAccentColor let bottomColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59) let topColor = bottomColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98) - bubbleColors = (topColor, bottomColor) + bubbleColors = [topColor.rgb, bottomColor.rgb] } if let initialAccentColor = accentColor { @@ -268,8 +268,7 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme defaultWallpaper = forcedWallpaper } - var outgoingBubbleFillColor: UIColor? - var outgoingBubbleFillGradientColor: UIColor? + var outgoingBubbleFillColors: [UIColor]? var outgoingPrimaryTextColor: UIColor? var outgoingSecondaryTextColor: UIColor? var outgoingLinkTextColor: UIColor? @@ -278,11 +277,10 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme var highlightedIncomingBubbleColor: UIColor? var highlightedOutgoingBubbleColor: UIColor? - if let bubbleColors = bubbleColors { - outgoingBubbleFillColor = bubbleColors.0 - outgoingBubbleFillGradientColor = bubbleColors.1 ?? bubbleColors.0 + if !bubbleColors.isEmpty { + outgoingBubbleFillColors = bubbleColors.map(UIColor.init(rgb:)) - let lightnessColor = bubbleColors.0.mixedWith(bubbleColors.1 ?? bubbleColors.0, alpha: 0.5) + let lightnessColor = UIColor(rgb: bubbleColors[0]).mixedWith(UIColor(rgb: bubbleColors.last ?? bubbleColors[0]), alpha: 0.5) if lightnessColor.lightness > 0.7 { outgoingPrimaryTextColor = UIColor(rgb: 0x000000) outgoingSecondaryTextColor = UIColor(rgb: 0x000000, alpha: 0.5) @@ -298,25 +296,24 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme } highlightedIncomingBubbleColor = accentColor?.withMultiplied(hue: 1.03, saturation: 0.463, brightness: 0.29) - highlightedOutgoingBubbleColor = outgoingBubbleFillColor?.withMultiplied(hue: 1.019, saturation: 0.609, brightness: 0.63) + highlightedOutgoingBubbleColor = outgoingBubbleFillColors?.first?.withMultiplied(hue: 1.019, saturation: 0.609, brightness: 0.63) } let incomingFillColor = mainBackgroundColor?.withMultipliedAlpha(0.9) chat = chat.withUpdated( defaultWallpaper: defaultWallpaper, + animateMessageColors: animateBubbleColors, message: chat.message.withUpdated( incoming: chat.message.incoming.withUpdated( bubble: chat.message.incoming.bubble.withUpdated( withWallpaper: chat.message.outgoing.bubble.withWallpaper.withUpdated( - fill: incomingFillColor, - gradientFill: incomingFillColor, + fill: incomingFillColor.flatMap({ [$0] }), highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor ), withoutWallpaper: chat.message.outgoing.bubble.withoutWallpaper.withUpdated( - fill: incomingFillColor, - gradientFill: incomingFillColor, + fill: incomingFillColor.flatMap({ [$0] }), highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor ) @@ -352,14 +349,12 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme outgoing: chat.message.outgoing.withUpdated( bubble: chat.message.outgoing.bubble.withUpdated( withWallpaper: chat.message.outgoing.bubble.withWallpaper.withUpdated( - fill: outgoingBubbleFillColor, - gradientFill: outgoingBubbleFillGradientColor, + fill: outgoingBubbleFillColors, highlightedFill: highlightedOutgoingBubbleColor, stroke: .clear ), withoutWallpaper: chat.message.outgoing.bubble.withoutWallpaper.withUpdated( - fill: outgoingBubbleFillColor, - gradientFill: outgoingBubbleFillGradientColor, + fill: outgoingBubbleFillColors, highlightedFill: highlightedOutgoingBubbleColor, stroke: .clear ) @@ -372,7 +367,7 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme accentControlColor: outgoingPrimaryTextColor, mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, - mediaControlInnerBackgroundColor: outgoingBubbleFillColor, + mediaControlInnerBackgroundColor: outgoingBubbleFillColors?.first, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, @@ -384,11 +379,11 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme ), freeform: chat.message.freeform.withUpdated( withWallpaper: chat.message.freeform.withWallpaper.withUpdated( - fill: mainBackgroundColor, + fill: mainBackgroundColor.flatMap({ [$0] }), highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor ), withoutWallpaper: chat.message.freeform.withoutWallpaper.withUpdated( - fill: mainBackgroundColor, + fill: mainBackgroundColor.flatMap({ [$0] }), highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor ) @@ -509,7 +504,9 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres let mainFreeTextColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.097, brightness: 0.56) let outgoingBubbleFillGradientColor = accentColor.withMultiplied(hue: 1.019, saturation: 0.731, brightness: 0.59) - let outgoingBubbleFillColor = outgoingBubbleFillGradientColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98) + + let outgoingBubbleFillColors: [UIColor] = [outgoingBubbleFillGradientColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98), outgoingBubbleFillGradientColor] + let outgoingScamColor = UIColor(rgb: 0xffffff) let outgoingPrimaryTextColor = UIColor(rgb: 0xffffff) let outgoingSecondaryTextColor = UIColor(rgb: 0xffffff, alpha: 0.5) @@ -700,9 +697,9 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres let incomingFillColor = mainBackgroundColor.withMultipliedAlpha(0.9) let message = PresentationThemeChatMessage( - incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: incomingFillColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: incomingFillColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor, shadow: nil)), primaryTextColor: .white, secondaryTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: UIColor(rgb: 0xff6767), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: accentColor, accentControlColor: accentColor, accentControlDisabledColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaActiveControlColor: accentColor, mediaInactiveControlColor: accentColor.withAlphaComponent(0.5), mediaControlInnerBackgroundColor: mainBackgroundColor, pendingActivityColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileTitleColor: accentColor, fileDescriptionColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileDurationColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.585, brightness: 0.23), polls: PresentationThemeChatBubblePolls(radioButton: accentColor.withMultiplied(hue: 0.995, saturation: 0.317, brightness: 0.51), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: mainSeparatorColor, bar: accentColor, barIconForeground: .white, barPositive: UIColor(rgb: 0x00A700), barNegative: UIColor(rgb: 0xFE3824)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: additionalBackgroundColor.withAlphaComponent(0.5), withoutWallpaper: additionalBackgroundColor.withAlphaComponent(0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor), actionButtonsTextColor: PresentationThemeVariableColor(color: .white), textSelectionColor: accentColor.withAlphaComponent(0.2), textSelectionKnobColor: accentColor), - outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, gradientFill: outgoingBubbleFillGradientColor, highlightedFill: highlightedOutgoingBubbleColor, stroke: outgoingBubbleFillColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColor, gradientFill: outgoingBubbleFillGradientColor, highlightedFill: highlightedOutgoingBubbleColor, stroke: outgoingBubbleFillColor, shadow: nil)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor.white.withAlphaComponent(0.5), scamColor: outgoingScamColor, textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, accentControlDisabledColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, mediaControlInnerBackgroundColor: outgoingBubbleFillColor, pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.804, brightness: 0.51), polls: PresentationThemeChatBubblePolls(radioButton: outgoingPrimaryTextColor, radioProgress: accentColor.withMultiplied(hue: 0.99, saturation: 0.56, brightness: 1.0), highlight: accentColor.withMultiplied(hue: 0.99, saturation: 0.56, brightness: 1.0).withAlphaComponent(0.12), separator: mainSeparatorColor, bar: outgoingPrimaryTextColor, barIconForeground: .clear, barPositive: outgoingPrimaryTextColor, barNegative: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: additionalBackgroundColor.withAlphaComponent(0.5), withoutWallpaper: additionalBackgroundColor.withAlphaComponent(0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor), actionButtonsTextColor: PresentationThemeVariableColor(color: .white), textSelectionColor: UIColor.white.withAlphaComponent(0.2), textSelectionKnobColor: UIColor.white), - freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: mainBackgroundColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: mainBackgroundColor, highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor, shadow: nil)), + incoming: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [incomingFillColor], highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [incomingFillColor], highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor, shadow: nil)), primaryTextColor: .white, secondaryTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), linkTextColor: accentColor, linkHighlightColor: accentColor.withAlphaComponent(0.5), scamColor: UIColor(rgb: 0xff6767), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: accentColor, accentControlColor: accentColor, accentControlDisabledColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaActiveControlColor: accentColor, mediaInactiveControlColor: accentColor.withAlphaComponent(0.5), mediaControlInnerBackgroundColor: mainBackgroundColor, pendingActivityColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileTitleColor: accentColor, fileDescriptionColor: mainSecondaryTextColor.withAlphaComponent(0.5), fileDurationColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.585, brightness: 0.23), polls: PresentationThemeChatBubblePolls(radioButton: accentColor.withMultiplied(hue: 0.995, saturation: 0.317, brightness: 0.51), radioProgress: accentColor, highlight: accentColor.withAlphaComponent(0.12), separator: mainSeparatorColor, bar: accentColor, barIconForeground: .white, barPositive: UIColor(rgb: 0x00A700), barNegative: UIColor(rgb: 0xFE3824)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: additionalBackgroundColor.withAlphaComponent(0.5), withoutWallpaper: additionalBackgroundColor.withAlphaComponent(0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor), actionButtonsTextColor: PresentationThemeVariableColor(color: .white), textSelectionColor: accentColor.withAlphaComponent(0.2), textSelectionKnobColor: accentColor), + outgoing: PresentationThemePartedColors(bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColors, highlightedFill: highlightedOutgoingBubbleColor, stroke: outgoingBubbleFillColors[0], shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: outgoingBubbleFillColors, highlightedFill: highlightedOutgoingBubbleColor, stroke: outgoingBubbleFillColors[0], shadow: nil)), primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, linkHighlightColor: UIColor.white.withAlphaComponent(0.5), scamColor: outgoingScamColor, textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: outgoingPrimaryTextColor, accentControlColor: outgoingPrimaryTextColor, accentControlDisabledColor: mainSecondaryTextColor.withAlphaComponent(0.5), mediaActiveControlColor: outgoingPrimaryTextColor, mediaInactiveControlColor: outgoingSecondaryTextColor, mediaControlInnerBackgroundColor: outgoingBubbleFillColors[0], pendingActivityColor: outgoingSecondaryTextColor, fileTitleColor: outgoingPrimaryTextColor, fileDescriptionColor: outgoingSecondaryTextColor, fileDurationColor: outgoingSecondaryTextColor, mediaPlaceholderColor: accentColor.withMultiplied(hue: 1.019, saturation: 0.804, brightness: 0.51), polls: PresentationThemeChatBubblePolls(radioButton: outgoingPrimaryTextColor, radioProgress: accentColor.withMultiplied(hue: 0.99, saturation: 0.56, brightness: 1.0), highlight: accentColor.withMultiplied(hue: 0.99, saturation: 0.56, brightness: 1.0).withAlphaComponent(0.12), separator: mainSeparatorColor, bar: outgoingPrimaryTextColor, barIconForeground: .clear, barPositive: outgoingPrimaryTextColor, barNegative: outgoingPrimaryTextColor), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: additionalBackgroundColor.withAlphaComponent(0.5), withoutWallpaper: additionalBackgroundColor.withAlphaComponent(0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: buttonStrokeColor), actionButtonsTextColor: PresentationThemeVariableColor(color: .white), textSelectionColor: UIColor.white.withAlphaComponent(0.2), textSelectionKnobColor: UIColor.white), + freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [mainBackgroundColor], highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [mainBackgroundColor], highlightedFill: highlightedIncomingBubbleColor, stroke: mainBackgroundColor, shadow: nil)), infoPrimaryTextColor: UIColor(rgb: 0xffffff), infoLinkTextColor: accentColor, outgoingCheckColor: outgoingCheckColor, @@ -788,6 +785,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres let chat = PresentationThemeChat( defaultWallpaper: defaultBuiltinWallpaper(data: .default, colors: [0x1b2836, 0x121a22, 0x1b2836, 0x121a22]), + animateMessageColors: false, message: message, serviceMessage: serviceMessage, inputPanel: inputPanel, diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index c0adf9e41d..413a7daeac 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -35,7 +35,7 @@ public let defaultServiceBackgroundColor = UIColor(rgb: 0x000000, alpha: 0.2) public let defaultPresentationTheme = makeDefaultDayPresentationTheme(serviceBackgroundColor: defaultServiceBackgroundColor, day: false, preview: false) public let defaultDayAccentColor = UIColor(rgb: 0x007ee5) -public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: (UIColor, UIColor?)?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, serviceBackgroundColor: UIColor?) -> PresentationTheme { +public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, serviceBackgroundColor: UIColor?) -> PresentationTheme { if (theme.referenceTheme != .day && theme.referenceTheme != .dayClassic) { return theme } @@ -52,14 +52,14 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti var suggestedWallpaper: TelegramWallpaper? var bubbleColors = bubbleColors - if bubbleColors == nil, editing { + if bubbleColors.isEmpty, editing { if day { let accentColor = accentColor ?? defaultDayAccentColor - bubbleColors = (accentColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98), accentColor) + bubbleColors = [accentColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98).rgb, accentColor.rgb] } else { if let accentColor = accentColor, !accentColor.alpha.isZero { let hsb = accentColor.hsb - bubbleColors = (UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0), nil) + bubbleColors = [UIColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0).rgb] if accentColor.lightness > 0.705 { outgoingAccent = UIColor(hue: hsb.0, saturation: min(1.0, hsb.1 * 1.1), brightness: min(hsb.2, 0.6), alpha: 1.0) } else { @@ -68,7 +68,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti suggestedWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: defaultBuiltinWallpaperGradientColors.map(\.rgb), settings: WallpaperSettings())) } else { - bubbleColors = (UIColor(rgb: 0xe1ffc7), nil) + bubbleColors = [UIColor(rgb: 0xe1ffc7).rgb] suggestedWallpaper = .gradient(TelegramWallpaper.Gradient(id: nil, colors: defaultBuiltinWallpaperGradientColors.map(\.rgb), settings: WallpaperSettings())) } } @@ -105,8 +105,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti } var incomingBubbleStrokeColor: UIColor? - var outgoingBubbleFillColor: UIColor? - var outgoingBubbleFillGradientColor: UIColor? + var outgoingBubbleFillColors: [UIColor]? var outgoingBubbleHighlightedFill: UIColor? var outgoingBubbleStrokeColor: UIColor? var outgoingPrimaryTextColor: UIColor? @@ -133,11 +132,11 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti outgoingBubbleStrokeColor = bubbleStrokeColor } - if let bubbleColors = bubbleColors { - var topBubbleColor = bubbleColors.0 - var bottomBubbleColor = bubbleColors.1 ?? bubbleColors.0 + if !bubbleColors.isEmpty { + //var topBubbleColor = UIColor(rgb: bubbleColors[0]) + //var bottomBubbleColor = UIColor(rgb: bubbleColors.last ?? bubbleColors[0]) - if topBubbleColor.rgb != bottomBubbleColor.rgb { + /*if topBubbleColor.rgb != bottomBubbleColor.rgb { let topBubbleColorLightness = topBubbleColor.lightness let bottomBubbleColorLightness = bottomBubbleColor.lightness if abs(topBubbleColorLightness - bottomBubbleColorLightness) > 0.7 { @@ -147,23 +146,22 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti bottomBubbleColor = bottomBubbleColor.withMultiplied(hue: 1.0, saturation: 1.0, brightness: 0.85) } } - } + }*/ - outgoingBubbleFillColor = topBubbleColor - outgoingBubbleFillGradientColor = bottomBubbleColor + outgoingBubbleFillColors = bubbleColors.map(UIColor.init(rgb:)) if day { outgoingBubbleStrokeColor = .clear } - outgoingBubbleHighlightedFill = outgoingBubbleFillColor?.withMultiplied(hue: 1.054, saturation: 1.589, brightness: 0.96) + outgoingBubbleHighlightedFill = outgoingBubbleFillColors?.first?.withMultiplied(hue: 1.054, saturation: 1.589, brightness: 0.96) - let lightnessColor = bubbleColors.0.mixedWith(bubbleColors.1 ?? bubbleColors.0, alpha: 0.5) + let lightnessColor = UIColor.average(of: bubbleColors.map(UIColor.init(rgb:))) if lightnessColor.lightness > 0.705 { let hueFactor: CGFloat = 0.75 let saturationFactor: CGFloat = 1.1 outgoingPrimaryTextColor = UIColor(rgb: 0x000000) - outgoingSecondaryTextColor = outgoingBubbleFillColor?.withMultiplied(hue: 1.344 * hueFactor, saturation: 4.554 * saturationFactor, brightness: 0.549).withAlphaComponent(0.8) + outgoingSecondaryTextColor = outgoingBubbleFillColors?.first?.withMultiplied(hue: 1.344 * hueFactor, saturation: 4.554 * saturationFactor, brightness: 0.549).withAlphaComponent(0.8) if let outgoingAccent = outgoingAccent { outgoingAccentTextColor = outgoingAccent @@ -192,13 +190,13 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti } outgoingPendingActivityColor = outgoingCheckColor - outgoingFileDescriptionColor = outgoingBubbleFillColor?.withMultiplied(hue: 1.257 * hueFactor, saturation: 1.842, brightness: 0.698) - outgoingFileDurationColor = outgoingBubbleFillColor?.withMultiplied(hue: 1.344 * hueFactor, saturation: 4.554, brightness: 0.549).withAlphaComponent(0.8) - outgoingMediaPlaceholderColor = outgoingBubbleFillColor?.withMultiplied(hue: 0.998, saturation: 1.129, brightness: 0.949) - outgoingPollsButtonColor = outgoingBubbleFillColor?.withMultiplied(hue: 1.207 * hueFactor, saturation: 1.721, brightness: 0.851) + outgoingFileDescriptionColor = outgoingBubbleFillColors?.first?.withMultiplied(hue: 1.257 * hueFactor, saturation: 1.842, brightness: 0.698) + outgoingFileDurationColor = outgoingBubbleFillColors?.first?.withMultiplied(hue: 1.344 * hueFactor, saturation: 4.554, brightness: 0.549).withAlphaComponent(0.8) + outgoingMediaPlaceholderColor = outgoingBubbleFillColors?.first?.withMultiplied(hue: 0.998, saturation: 1.129, brightness: 0.949) + outgoingPollsButtonColor = outgoingBubbleFillColors?.first?.withMultiplied(hue: 1.207 * hueFactor, saturation: 1.721, brightness: 0.851) if day { - if let distance = outgoingBubbleFillColor?.distance(to: UIColor(rgb: 0xffffff)), distance < 200 { + if let distance = outgoingBubbleFillColors?.first?.distance(to: UIColor(rgb: 0xffffff)), distance < 200 { outgoingBubbleStrokeColor = UIColor(rgb: 0xc8c7cc) } } @@ -214,7 +212,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti outgoingFileTitleColor = outgoingPrimaryTextColor outgoingFileDescriptionColor = outgoingSecondaryTextColor outgoingFileDurationColor = outgoingSecondaryTextColor - outgoingMediaPlaceholderColor = outgoingBubbleFillColor?.withMultipliedBrightnessBy(0.95) + outgoingMediaPlaceholderColor = outgoingBubbleFillColors?.first?.withMultipliedBrightnessBy(0.95) outgoingPollsButtonColor = outgoingSecondaryTextColor outgoingPollsProgressColor = outgoingPrimaryTextColor outgoingSelectionBaseColor = UIColor(rgb: 0xffffff) @@ -238,6 +236,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti chat = chat.withUpdated( defaultWallpaper: defaultWallpaper, + animateMessageColors: animateBubbleColors, message: chat.message.withUpdated( incoming: chat.message.incoming.withUpdated( bubble: chat.message.incoming.bubble.withUpdated( @@ -268,14 +267,12 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti outgoing: chat.message.outgoing.withUpdated( bubble: chat.message.outgoing.bubble.withUpdated( withWallpaper: chat.message.outgoing.bubble.withWallpaper.withUpdated( - fill: outgoingBubbleFillColor, - gradientFill: outgoingBubbleFillGradientColor, + fill: outgoingBubbleFillColors, highlightedFill: outgoingBubbleHighlightedFill, stroke: outgoingBubbleStrokeColor ), withoutWallpaper: chat.message.outgoing.bubble.withoutWallpaper.withUpdated( - fill: outgoingBubbleFillColor, - gradientFill: outgoingBubbleFillGradientColor, + fill: outgoingBubbleFillColors, highlightedFill: outgoingBubbleHighlightedFill, stroke: outgoingBubbleStrokeColor ) @@ -532,7 +529,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio let message = PresentationThemeChatMessage( incoming: PresentationThemePartedColors( - bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: bubbleStrokeColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: bubbleStrokeColor, shadow: nil)), + bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xffffff)], highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: bubbleStrokeColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xffffff)], highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: bubbleStrokeColor, shadow: nil)), primaryTextColor: UIColor(rgb: 0x000000), secondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), linkTextColor: UIColor(rgb: 0x004bad), @@ -554,7 +551,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: serviceBackgroundColor, withoutWallpaper: UIColor(rgb: 0x596e89, alpha: 0.35)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: .clear), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0x007ee5, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0x007ee5)), outgoing: PresentationThemePartedColors( - bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xe1ffc7), highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: bubbleStrokeColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xe1ffc7), highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: bubbleStrokeColor, shadow: nil)), + bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xe1ffc7)], highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: bubbleStrokeColor, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xe1ffc7)], highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: bubbleStrokeColor, shadow: nil)), primaryTextColor: UIColor(rgb: 0x000000), secondaryTextColor: UIColor(rgb: 0x008c09, alpha: 0.8), linkTextColor: UIColor(rgb: 0x004bad), @@ -578,7 +575,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xbbde9f), textSelectionKnobColor: UIColor(rgb: 0x3fc33b)), - freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86a9c9, alpha: 0.5), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86a9c9, alpha: 0.5), shadow: nil)), + freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xffffff)], highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86a9c9, alpha: 0.5), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xffffff)], highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86a9c9, alpha: 0.5), shadow: nil)), infoPrimaryTextColor: UIColor(rgb: 0x000000), infoLinkTextColor: UIColor(rgb: 0x004bad), outgoingCheckColor: UIColor(rgb: 0x19c700), @@ -597,7 +594,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio let messageDay = PresentationThemeChatMessage( incoming: PresentationThemePartedColors( - bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xffffff), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xf1f1f4), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xf1f1f4), shadow: nil)), + bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xffffff)], highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xffffff), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xf1f1f4)], highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xf1f1f4), shadow: nil)), primaryTextColor: UIColor(rgb: 0x000000), secondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), linkTextColor: UIColor(rgb: 0x004bad), @@ -622,7 +619,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio textSelectionColor: UIColor(rgb: 0x007ee5, alpha: 0.3), textSelectionKnobColor: UIColor(rgb: 0x007ee5)), outgoing: PresentationThemePartedColors( - bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x57b2e0), gradientFill: UIColor(rgb: 0x007ee5), highlightedFill: UIColor(rgb: 0x57b2e0).withMultipliedBrightnessBy(0.7), stroke: .clear, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x57b2e0), gradientFill: UIColor(rgb: 0x007ee5), highlightedFill: UIColor(rgb: 0x57b2e0).withMultipliedBrightnessBy(0.7), stroke: .clear, shadow: nil)), + bubble: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0x57b2e0), UIColor(rgb: 0x007ee5)], highlightedFill: UIColor(rgb: 0x57b2e0).withMultipliedBrightnessBy(0.7), stroke: .clear, shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0x57b2e0), UIColor(rgb: 0x007ee5)], highlightedFill: UIColor(rgb: 0x57b2e0).withMultipliedBrightnessBy(0.7), stroke: .clear, shadow: nil)), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.65), linkTextColor: UIColor(rgb: 0xffffff), @@ -646,7 +643,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio actionButtonsTextColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0xffffff), withoutWallpaper: UIColor(rgb: 0x007ee5)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)), - freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xe5e5ea), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xe5e5ea), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xe5e5ea), highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xe5e5ea), shadow: nil)), + freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xe5e5ea)], highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xe5e5ea), shadow: nil), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: [UIColor(rgb: 0xe5e5ea)], highlightedFill: UIColor(rgb: 0xdadade), stroke: UIColor(rgb: 0xe5e5ea), shadow: nil)), infoPrimaryTextColor: UIColor(rgb: 0x000000), infoLinkTextColor: UIColor(rgb: 0x004bad), outgoingCheckColor: UIColor(rgb: 0xffffff), @@ -742,6 +739,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio let chat = PresentationThemeChat( defaultWallpaper: day ? .color(0xffffff) : defaultPatternWallpaper, + animateMessageColors: false, message: day ? messageDay : message, serviceMessage: day ? serviceMessageDay : serviceMessage, inputPanel: inputPanel, @@ -754,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/MakePresentationTheme.swift b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift index db916a3486..0f5b73c3a0 100644 --- a/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/MakePresentationTheme.swift @@ -19,46 +19,46 @@ public func makeDefaultPresentationTheme(reference: PresentationBuiltinThemeRefe return theme } -public func customizePresentationTheme(_ theme: PresentationTheme, editing: Bool, title: String? = nil, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: (UIColor, UIColor?)?, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme { - if accentColor == nil && bubbleColors == nil && backgroundColors.isEmpty && wallpaper == nil { +public func customizePresentationTheme(_ theme: PresentationTheme, editing: Bool, title: String? = nil, accentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil) -> PresentationTheme { + if accentColor == nil && bubbleColors.isEmpty && backgroundColors.isEmpty && wallpaper == nil { return theme } switch theme.referenceTheme { case .day, .dayClassic: - return customizeDefaultDayTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper, serviceBackgroundColor: nil) + return customizeDefaultDayTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, serviceBackgroundColor: nil) case .night: - return customizeDefaultDarkPresentationTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper, baseColor: baseColor) + return customizeDefaultDarkPresentationTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, baseColor: baseColor) case .nightAccent: - return customizeDefaultDarkTintedPresentationTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper, baseColor: baseColor) + return customizeDefaultDarkTintedPresentationTheme(theme: theme, editing: editing, title: title, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors ?? false, wallpaper: wallpaper, baseColor: baseColor) } } public func makePresentationTheme(settings: TelegramThemeSettings, title: String? = nil, serviceBackgroundColor: UIColor? = nil) -> PresentationTheme? { let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: serviceBackgroundColor, preview: false) - return customizePresentationTheme(defaultTheme, editing: true, title: title, accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors.flatMap { (UIColor(argb: $0.top), UIColor(argb: $0.bottom)) }, wallpaper: settings.wallpaper) + return customizePresentationTheme(defaultTheme, editing: true, title: title, accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper) } -public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: (UIColor, UIColor?)? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, preview: Bool = false) -> PresentationTheme? { +public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, preview: Bool = false) -> PresentationTheme? { let theme: PresentationTheme switch themeReference { case let .builtin(reference): let defaultTheme = makeDefaultPresentationTheme(reference: reference, extendingThemeReference: extendingThemeReference, serviceBackgroundColor: serviceBackgroundColor, preview: preview) - theme = customizePresentationTheme(defaultTheme, editing: true, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper, baseColor: baseColor) + theme = customizePresentationTheme(defaultTheme, editing: true, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper, baseColor: baseColor) case let .local(info): if let path = mediaBox.completedResourcePath(info.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) { - theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper) + theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper) } else { return nil } case let .cloud(info): if let settings = info.theme.settings { - if let loadedTheme = makePresentationTheme(mediaBox: mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), extendingThemeReference: themeReference, accentColor: accentColor ?? UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: bubbleColors ?? settings.messageColors.flatMap { (UIColor(argb: $0.top), UIColor(argb: $0.bottom)) }, wallpaper: wallpaper ?? settings.wallpaper, serviceBackgroundColor: serviceBackgroundColor, preview: preview) { + if let loadedTheme = makePresentationTheme(mediaBox: mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), extendingThemeReference: themeReference, accentColor: accentColor ?? UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: bubbleColors.isEmpty ? settings.messageColors : bubbleColors, animateBubbleColors: animateBubbleColors ?? settings.animateMessageColors, wallpaper: wallpaper ?? settings.wallpaper, serviceBackgroundColor: serviceBackgroundColor, preview: preview) { theme = loadedTheme } else { return nil } } else if let file = info.theme.file, let path = mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let loadedTheme = makePresentationTheme(data: data, themeReference: themeReference, resolvedWallpaper: info.resolvedWallpaper) { - theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, wallpaper: wallpaper) + theme = customizePresentationTheme(loadedTheme, editing: false, accentColor: accentColor, backgroundColors: backgroundColors, bubbleColors: bubbleColors, animateBubbleColors: animateBubbleColors, wallpaper: wallpaper) } else { return nil } diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index 1c84b844d6..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 } @@ -225,7 +229,7 @@ public final class InitialPresentationDataAndSettings { } } -public func currentPresentationDataAndSettings(accountManager: AccountManager, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Signal { +public func currentPresentationDataAndSettings(accountManager: AccountManager, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Signal { return accountManager.transaction { transaction -> InitialPresentationDataAndSettings in let localizationSettings: LocalizationSettings? if let current = transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings { @@ -292,7 +296,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager, s } let effectiveColors = themeSettings.themeSpecificAccentColors[effectiveTheme.index] - let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors, baseColor: effectiveColors?.baseColor) ?? defaultPresentationTheme + let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], baseColor: effectiveColors?.baseColor) ?? defaultPresentationTheme let effectiveChatWallpaper: TelegramWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: effectiveTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[effectiveTheme.index]) ?? theme.chat.defaultWallpaper @@ -561,7 +565,7 @@ public func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, mediaBox: M } } -public func updatedPresentationData(accountManager: AccountManager, applicationInForeground: Signal, systemUserInterfaceStyle: Signal) -> Signal { +public func updatedPresentationData(accountManager: AccountManager, applicationInForeground: Signal, systemUserInterfaceStyle: Signal) -> Signal { return combineLatest(accountManager.sharedData(keys: [SharedDataKeys.localizationSettings, ApplicationSpecificSharedDataKeys.presentationThemeSettings, ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), systemUserInterfaceStyle) |> mapToSignal { sharedData, systemUserInterfaceStyle -> Signal in let themeSettings: PresentationThemeSettings @@ -583,7 +587,7 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI if let themeSpecificWallpaper = themeSpecificWallpaper { currentWallpaper = themeSpecificWallpaper } else { - let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors, wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor) ?? defaultPresentationTheme + let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor) ?? defaultPresentationTheme currentWallpaper = theme.chat.defaultWallpaper } @@ -619,7 +623,7 @@ public func updatedPresentationData(accountManager: AccountManager, applicationI effectiveColors = nil } - let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors, wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme + let themeValue = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: effectiveTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: serviceBackgroundColor) ?? defaultPresentationTheme if autoNightModeTriggered && !switchedToNightModeWallpaper { switch effectiveChatWallpaper { diff --git a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift index 71b1a44f13..0c3f6c5391 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift @@ -635,22 +635,20 @@ public struct PresentationThemeBubbleShadow { } public final class PresentationThemeBubbleColorComponents { - public let fill: UIColor - public let gradientFill: UIColor + public let fill: [UIColor] public let highlightedFill: UIColor public let stroke: UIColor public let shadow: PresentationThemeBubbleShadow? - public init(fill: UIColor, gradientFill: UIColor? = nil, highlightedFill: UIColor, stroke: UIColor, shadow: PresentationThemeBubbleShadow?) { + public init(fill: [UIColor], highlightedFill: UIColor, stroke: UIColor, shadow: PresentationThemeBubbleShadow?) { self.fill = fill - self.gradientFill = gradientFill ?? fill self.highlightedFill = highlightedFill self.stroke = stroke self.shadow = shadow } - public func withUpdated(fill: UIColor? = nil, gradientFill: UIColor? = nil, highlightedFill: UIColor? = nil, stroke: UIColor? = nil) -> PresentationThemeBubbleColorComponents { - return PresentationThemeBubbleColorComponents(fill: fill ?? self.fill, gradientFill: gradientFill ?? self.gradientFill, highlightedFill: highlightedFill ?? self.highlightedFill, stroke: stroke ?? self.stroke, shadow: self.shadow) + public func withUpdated(fill: [UIColor]? = nil, highlightedFill: UIColor? = nil, stroke: UIColor? = nil) -> PresentationThemeBubbleColorComponents { + return PresentationThemeBubbleColorComponents(fill: fill ?? self.fill, highlightedFill: highlightedFill ?? self.highlightedFill, stroke: stroke ?? self.stroke, shadow: self.shadow) } } @@ -1068,6 +1066,7 @@ public final class PresentationThemeChatHistoryNavigation { public final class PresentationThemeChat { public let defaultWallpaper: TelegramWallpaper + public let animateMessageColors: Bool public let message: PresentationThemeChatMessage public let serviceMessage: PresentationThemeServiceMessage public let inputPanel: PresentationThemeChatInputPanel @@ -1075,8 +1074,9 @@ public final class PresentationThemeChat { public let inputButtonPanel: PresentationThemeInputButtonPanel public let historyNavigation: PresentationThemeChatHistoryNavigation - public init(defaultWallpaper: TelegramWallpaper, message: PresentationThemeChatMessage, serviceMessage: PresentationThemeServiceMessage, inputPanel: PresentationThemeChatInputPanel, inputMediaPanel: PresentationThemeInputMediaPanel, inputButtonPanel: PresentationThemeInputButtonPanel, historyNavigation: PresentationThemeChatHistoryNavigation) { + public init(defaultWallpaper: TelegramWallpaper, animateMessageColors: Bool, message: PresentationThemeChatMessage, serviceMessage: PresentationThemeServiceMessage, inputPanel: PresentationThemeChatInputPanel, inputMediaPanel: PresentationThemeInputMediaPanel, inputButtonPanel: PresentationThemeInputButtonPanel, historyNavigation: PresentationThemeChatHistoryNavigation) { self.defaultWallpaper = defaultWallpaper + self.animateMessageColors = animateMessageColors self.message = message self.serviceMessage = serviceMessage self.inputPanel = inputPanel @@ -1085,8 +1085,8 @@ public final class PresentationThemeChat { self.historyNavigation = historyNavigation } - public func withUpdated(defaultWallpaper: TelegramWallpaper? = nil, message: PresentationThemeChatMessage? = nil, serviceMessage: PresentationThemeServiceMessage? = nil, inputPanel: PresentationThemeChatInputPanel? = nil, inputMediaPanel: PresentationThemeInputMediaPanel? = nil, inputButtonPanel: PresentationThemeInputButtonPanel? = nil, historyNavigation: PresentationThemeChatHistoryNavigation? = nil) -> PresentationThemeChat { - return PresentationThemeChat(defaultWallpaper: defaultWallpaper ?? self.defaultWallpaper, message: message ?? self.message, serviceMessage: serviceMessage ?? self.serviceMessage, inputPanel: inputPanel ?? self.inputPanel, inputMediaPanel: inputMediaPanel ?? self.inputMediaPanel, inputButtonPanel: inputButtonPanel ?? self.inputButtonPanel, historyNavigation: historyNavigation ?? self.historyNavigation) + public func withUpdated(defaultWallpaper: TelegramWallpaper? = nil, animateMessageColors: Bool? = nil, message: PresentationThemeChatMessage? = nil, serviceMessage: PresentationThemeServiceMessage? = nil, inputPanel: PresentationThemeChatInputPanel? = nil, inputMediaPanel: PresentationThemeInputMediaPanel? = nil, inputButtonPanel: PresentationThemeInputButtonPanel? = nil, historyNavigation: PresentationThemeChatHistoryNavigation? = nil) -> PresentationThemeChat { + return PresentationThemeChat(defaultWallpaper: defaultWallpaper ?? self.defaultWallpaper, animateMessageColors: animateMessageColors ?? self.animateMessageColors, message: message ?? self.message, serviceMessage: serviceMessage ?? self.serviceMessage, inputPanel: inputPanel ?? self.inputPanel, inputMediaPanel: inputMediaPanel ?? self.inputMediaPanel, inputButtonPanel: inputButtonPanel ?? self.inputButtonPanel, historyNavigation: historyNavigation ?? self.historyNavigation) } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift index 1a04568e4a..6af0656c74 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift @@ -33,6 +33,37 @@ private func encodeColor(_ values: inout KeyedEncodingContainer, _ val } } +private func decodeColorList(_ values: KeyedDecodingContainer, _ key: Key) throws -> [UIColor] { + let colorValues = try values.decode([String].self, forKey: key) + + var result: [UIColor] = [] + for value in colorValues { + if value.lowercased() == "clear" { + result.append(UIColor.clear) + } else if let color = UIColor(hexString: value) { + result.append(color) + } else { + throw PresentationThemeDecodingError.generic + } + } + + return result +} + +private func encodeColorList(_ values: inout KeyedEncodingContainer, _ colors: [UIColor], _ key: Key) throws { + var stringList: [String] = [] + for value in colors { + if value == UIColor.clear { + stringList.append("clear") + } else if value.alpha < 1.0 { + stringList.append(String(format: "%08x", value.argb)) + } else { + stringList.append(String(format: "%06x", value.rgb)) + } + } + try values.encode(stringList, forKey: key) +} + extension TelegramWallpaper: Codable { public init(from decoder: Decoder) throws { let values = try decoder.singleValueContainer() @@ -56,52 +87,41 @@ extension TelegramWallpaper: Codable { blur = true } - if components.count >= 2 && components.count <= 5 && [6, 8].contains(components[0].count) && !optionKeys.contains(components[0]) && [6, 8].contains(components[1].count) && !optionKeys.contains(components[1]), let topColor = UIColor(hexString: components[0]), let bottomColor = UIColor(hexString: components[1]) { - var rotation: Int32? - if components.count > 2, components[2].count <= 3, let value = Int32(components[2]) { - if value >= 0 && value < 360 { - rotation = value - } + var slug: String? + var colors: [UIColor] = [] + var intensity: Int32? + var rotation: Int32? + for i in 0 ..< components.count { + let component = components[i] + if optionKeys.contains(component) { + continue } - self = .gradient(TelegramWallpaper.Gradient(id: nil, colors: [topColor.argb, bottomColor.argb], settings: WallpaperSettings(blur: blur, motion: motion, rotation: rotation))) - } else { - var slug: String? - var colors: [UInt32] = [] - var intensity: Int32? - var rotation: Int32? - - if !components.isEmpty { - slug = components[0] - } - if components.count > 1 { - for i in 1 ..< components.count { - let component = components[i] - if optionKeys.contains(component) { - continue + if i == 0 && component.count > 8 { + slug = component + } else if [6, 8].contains(component.count), let color = UIColor(hexString: component) { + colors.append(color) + } else if component.count <= 4, let value = Int32(component) { + if intensity == nil { + if value >= -100 && value <= 100 { + intensity = value + } else { + intensity = 50 } - if [6, 8].contains(component.count), let value = UIColor(hexString: component) { - colors.append(value.rgb) - } else if component.count <= 3, let value = Int32(component) { - if intensity == nil { - if value >= 0 && value <= 100 { - intensity = value - } else { - intensity = 50 - } - } else if rotation == nil { - if value >= 0 && value < 360 { - rotation = value - } - } + } else if rotation == nil { + if value >= 0 && value < 360 { + rotation = value } } } - if let slug = slug { - self = .file(TelegramWallpaper.File(id: 0, accessHash: 0, isCreator: false, isDefault: false, isPattern: !colors.isEmpty, isDark: false, slug: slug, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: WallpaperDataResource(slug: slug), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(blur: blur, motion: motion, colors: colors, intensity: intensity, rotation: rotation))) - } else { - throw PresentationThemeDecodingError.generic - } + } + + if let slug = slug { + self = .file(TelegramWallpaper.File(id: 0, accessHash: 0, isCreator: false, isDefault: false, isPattern: !colors.isEmpty, isDark: false, slug: slug, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: WallpaperDataResource(slug: slug), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(blur: blur, motion: motion, colors: colors.map { $0.argb }, intensity: intensity, rotation: rotation))) + } else if colors.count > 1 { + self = .gradient(TelegramWallpaper.Gradient(id: nil, colors: colors.map { $0.argb }, settings: WallpaperSettings(blur: blur, motion: motion, rotation: rotation))) + } else { + throw PresentationThemeDecodingError.generic } } } @@ -1070,22 +1090,30 @@ extension PresentationThemeBubbleColorComponents: Codable { case highlightedBg case stroke case shadow + case bgList } public convenience init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) let codingPath = decoder.codingPath.map { $0.stringValue }.joined(separator: ".") - - var fillColor = try decodeColor(values, .bg) - var gradientColor = try decodeColor(values, .gradientBg, decoder: decoder, fallbackKey: "\(codingPath).bg") - if gradientColor.rgb != fillColor.rgb { - fillColor = fillColor.withAlphaComponent(1.0) - gradientColor = gradientColor.withAlphaComponent(1.0) + + let fill: [UIColor] + + if let bgList = try? decodeColorList(values, .bgList) { + fill = bgList + } else { + var fillColor = try decodeColor(values, .bg) + var gradientColor = try decodeColor(values, .gradientBg, decoder: decoder, fallbackKey: "\(codingPath).bg") + if gradientColor.rgb != fillColor.rgb { + fillColor = fillColor.withAlphaComponent(1.0) + gradientColor = gradientColor.withAlphaComponent(1.0) + } + + fill = [fillColor, gradientColor] } - + self.init( - fill: fillColor, - gradientFill: gradientColor, + fill: fill, highlightedFill: try decodeColor(values, .highlightedBg), stroke: try decodeColor(values, .stroke), shadow: try? values.decode(PresentationThemeBubbleShadow.self, forKey: .shadow) @@ -1094,8 +1122,17 @@ extension PresentationThemeBubbleColorComponents: Codable { public func encode(to encoder: Encoder) throws { var values = encoder.container(keyedBy: CodingKeys.self) - try encodeColor(&values, self.fill, .bg) - try encodeColor(&values, self.gradientFill, .gradientBg) + if self.fill.count <= 2 { + if self.fill.count > 1 { + try encodeColor(&values, self.fill[0], .bg) + try encodeColor(&values, self.fill[1], .gradientBg) + } else { + try encodeColor(&values, self.fill[0], .bg) + try encodeColor(&values, self.fill[0], .gradientBg) + } + } else { + try encodeColorList(&values, self.fill, .bgList) + } try encodeColor(&values, self.highlightedFill, .highlightedBg) try encodeColor(&values, self.stroke, .stroke) } @@ -1611,6 +1648,7 @@ extension PresentationThemeChat: Codable { enum CodingKeys: String, CodingKey { case defaultWallpaper case message + case animateMessageColors case serviceMessage case inputPanel case inputMediaPanel @@ -1629,6 +1667,7 @@ extension PresentationThemeChat: Codable { } self.init(defaultWallpaper: wallpaper, + animateMessageColors: (try? values.decode(Bool.self, forKey: .animateMessageColors)) ?? false, message: try values.decode(PresentationThemeChatMessage.self, forKey: .message), serviceMessage: try values.decode(PresentationThemeServiceMessage.self, forKey: .serviceMessage), inputPanel: try values.decode(PresentationThemeChatInputPanel.self, forKey: .inputPanel), @@ -1640,6 +1679,7 @@ extension PresentationThemeChat: Codable { public func encode(to encoder: Encoder) throws { var values = encoder.container(keyedBy: CodingKeys.self) try values.encode(self.defaultWallpaper, forKey: .defaultWallpaper) + try values.encode(self.animateMessageColors, forKey: .animateMessageColors) try values.encode(self.message, forKey: .message) try values.encode(self.serviceMessage, forKey: .serviceMessage) try values.encode(self.inputPanel, forKey: .inputPanel) diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCoder.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCoder.swift index 474678ca56..95fb03aa9d 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeCoder.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeCoder.swift @@ -465,6 +465,23 @@ class PresentationThemeDecoding: Decoder { } guard let topContainer = self.storage.topContainer as? [Any] else { + if let topContainer = self.storage.topContainer as? [String : Any] { + let sortedKeys = topContainer.keys.sorted(by: { lhs, rhs in + if let lhsValue = Int(lhs), let rhsValue = Int(rhs), lhsValue < rhsValue { + return true + } else { + return false + } + }) + var array: [Any] = [] + for key in sortedKeys { + if let value = topContainer[key] { + array.append(value) + } + } + return PresentationThemeUnkeyedDecodingContainer(referencing: self, wrapping: array) + } + throw PresentationThemeDecodingError.typeMismatch } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 4e654f2613..a0e1b155b6 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -199,18 +199,23 @@ public final class PrincipalThemeEssentialGraphics { let incoming: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.incoming.bubble.withoutWallpaper : theme.message.incoming.bubble.withWallpaper let outgoing: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.outgoing.bubble.withoutWallpaper : theme.message.outgoing.bubble.withWallpaper - var incomingGradientColors: (UIColor, UIColor)? - if incoming.fill.rgb != incoming.gradientFill.rgb { - incomingGradientColors = (incoming.fill, incoming.gradientFill) + var incomingGradientColors: [UIColor]? + if Set(incoming.fill.map(\.argb)).count > 1 { + incomingGradientColors = incoming.fill } if let incomingGradientColors = incomingGradientColors { self.incomingBubbleGradientImage = generateImage(CGSize(width: 1.0, height: 512.0), opaque: true, scale: 1.0, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - var locations: [CGFloat] = [0.0, 1.0] - let colors = [incomingGradientColors.0.cgColor, incomingGradientColors.1.cgColor] as NSArray + var locations: [CGFloat] = [] + var colors: [CGColor] = [] + for i in 0 ..< incomingGradientColors.count { + let t = CGFloat(i) / CGFloat(incomingGradientColors.count - 1) + locations.append(t) + colors.append(incomingGradientColors[i].cgColor) + } let colorSpace = deviceColorSpace - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: &locations)! + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)! context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) }) @@ -218,18 +223,23 @@ public final class PrincipalThemeEssentialGraphics { self.incomingBubbleGradientImage = nil } - var outgoingGradientColors: (UIColor, UIColor)? - if outgoing.fill.rgb != outgoing.gradientFill.rgb { - outgoingGradientColors = (outgoing.fill, outgoing.gradientFill) + var outgoingGradientColors: [UIColor]? + if Set(outgoing.fill.map(\.argb)).count > 1 { + outgoingGradientColors = outgoing.fill } if let outgoingGradientColors = outgoingGradientColors { self.outgoingBubbleGradientImage = generateImage(CGSize(width: 1.0, height: 512.0), opaque: true, scale: 1.0, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - var locations: [CGFloat] = [0.0, 1.0] - let colors = [outgoingGradientColors.0.cgColor, outgoingGradientColors.1.cgColor] as NSArray + var locations: [CGFloat] = [] + var colors: [CGColor] = [] + for i in 0 ..< outgoingGradientColors.count { + let t = CGFloat(i) / CGFloat(outgoingGradientColors.count - 1) + locations.append(t) + colors.append(outgoingGradientColors[i].cgColor) + } let colorSpace = deviceColorSpace - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: &locations)! + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)! context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) }) @@ -249,73 +259,73 @@ public final class PrincipalThemeEssentialGraphics { if preview { self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: UIColor.black, strokeColor: UIColor.clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor, width: 11.0)! self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor, width: 11.0)! self.chatMessageBackgroundIncomingHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedBothHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingMergedSideHighlightedImage = emptyImage self.chatMessageBackgroundOutgoingHighlightedImage = emptyImage self.chatMessageBackgroundOutgoingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = emptyImage self.chatMessageBackgroundOutgoingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = emptyImage self.chatMessageBackgroundOutgoingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = emptyImage self.chatMessageBackgroundOutgoingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = emptyImage - self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = emptyImage self.checkMediaFullImage = emptyImage self.checkMediaPartialImage = emptyImage @@ -361,70 +371,70 @@ public final class PrincipalThemeEssentialGraphics { } else { self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedTopMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: false), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedTopOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedTopShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: false), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedTopSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .top(side: true), theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedTopSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedTopSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: true), theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedBottomMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .bottom, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedBottomOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedBottomShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .bottom, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedBothMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .both, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedBothOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedBothShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundIncomingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundIncomingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundIncomingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundIncomingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill[0], strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMergedSideMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .side, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) - self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) - self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) + self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) + self.chatMessageBackgroundOutgoingMergedSideOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) + self.chatMessageBackgroundOutgoingMergedSideShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill[0], strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundIncomingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .side, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index f2e693ae52..5ad361316f 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -152,14 +152,14 @@ public struct PresentationResourcesChat { public static func chatInstantVideoBackgroundImage(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? { let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatInstantVideoWithoutWallpaperBackgroundImage : PresentationResourceKey.chatInstantVideoWithWallpaperBackgroundImage return theme.image(key.rawValue, { theme in - return generateInstantVideoBackground(fillColor: theme.chat.message.freeform.withWallpaper.fill, strokeColor: theme.chat.message.freeform.withWallpaper.stroke) + return generateInstantVideoBackground(fillColor: theme.chat.message.freeform.withWallpaper.fill[0], strokeColor: theme.chat.message.freeform.withWallpaper.stroke) }) } public static func chatActionPhotoBackgroundImage(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? { let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatActionPhotoWithoutWallpaperBackgroundImage : PresentationResourceKey.chatActionPhotoWithWallpaperBackgroundImage return theme.image(key.rawValue, { theme in - return generateActionPhotoBackground(fillColor: theme.chat.message.freeform.withWallpaper.fill, strokeColor: theme.chat.message.freeform.withWallpaper.stroke) + return generateActionPhotoBackground(fillColor: theme.chat.message.freeform.withWallpaper.fill[0], strokeColor: theme.chat.message.freeform.withWallpaper.stroke) }) } @@ -178,7 +178,7 @@ public struct PresentationResourcesChat { public static func chatInfoItemBackgroundImageWithoutWallpaper(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInfoItemBackgroundImageWithoutWallpaper.rawValue, { theme in - return messageSingleBubbleLikeImage(fillColor: theme.chat.message.incoming.bubble.withoutWallpaper.fill, strokeColor: theme.chat.message.incoming.bubble.withoutWallpaper.stroke) + return messageSingleBubbleLikeImage(fillColor: theme.chat.message.incoming.bubble.withoutWallpaper.fill[0], strokeColor: theme.chat.message.incoming.bubble.withoutWallpaper.stroke) }) } @@ -186,7 +186,7 @@ public struct PresentationResourcesChat { let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatInfoItemBackgroundImageWithoutWallpaper : PresentationResourceKey.chatInfoItemBackgroundImageWithWallpaper return theme.image(key.rawValue, { theme in let components: PresentationThemeBubbleColorComponents = wallpaper ? theme.chat.message.incoming.bubble.withWallpaper : theme.chat.message.incoming.bubble.withoutWallpaper - return messageSingleBubbleLikeImage(fillColor: components.fill, strokeColor: components.stroke) + return messageSingleBubbleLikeImage(fillColor: components.fill[0], strokeColor: components.stroke) }) } @@ -827,7 +827,7 @@ public struct PresentationResourcesChat { public static func chatMessageAttachedContentHighlightedButtonIconInstantIncoming(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? { let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantIncomingWithoutWallpaper : PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantIncomingWithWallpaper return theme.image(key.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: bubbleColorComponents(theme: theme, incoming: true, wallpaper: wallpaper).fill) + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: bubbleColorComponents(theme: theme, incoming: true, wallpaper: wallpaper).fill[0]) }) } @@ -840,7 +840,7 @@ public struct PresentationResourcesChat { public static func chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? { let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantOutgoingWithoutWallpaper : PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantOutgoingWithWallpaper return theme.image(key.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: bubbleColorComponents(theme: theme, incoming: false, wallpaper: wallpaper).fill) + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: bubbleColorComponents(theme: theme, incoming: false, wallpaper: wallpaper).fill[0]) }) } 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/Images.xcassets/Call/Check.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/Check.imageset/Contents.json new file mode 100644 index 0000000000..ed9cc18e76 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/Check.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "RecordCheck.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/Check.imageset/RecordCheck.png b/submodules/TelegramUI/Images.xcassets/Call/Check.imageset/RecordCheck.png new file mode 100644 index 0000000000..913252c8f9 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Call/Check.imageset/RecordCheck.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/OverscrollArrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/OverscrollArrow.imageset/Contents.json new file mode 100644 index 0000000000..85134c7af4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/OverscrollArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "drag.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/OverscrollArrow.imageset/drag.svg b/submodules/TelegramUI/Images.xcassets/Chat/OverscrollArrow.imageset/drag.svg new file mode 100644 index 0000000000..7be68b60aa --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/OverscrollArrow.imageset/drag.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Images.xcassets/Chat/OverscrollFolder.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/OverscrollFolder.imageset/Contents.json new file mode 100644 index 0000000000..e8bb9f9c6d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/OverscrollFolder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Folder.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/OverscrollFolder.imageset/Folder.svg b/submodules/TelegramUI/Images.xcassets/Chat/OverscrollFolder.imageset/Folder.svg new file mode 100644 index 0000000000..458fe8b374 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/OverscrollFolder.imageset/Folder.svg @@ -0,0 +1,3 @@ + + + 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/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 30015f24d7..9d400eda65 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -290,6 +290,28 @@ public final class AccountContextImpl: AccountContext { return context.maxReadOutgoingMessageId } } + + public func chatLocationUnreadCount(for location: ChatLocation, contextHolder: Atomic) -> Signal { + switch location { + case let .peer(peerId): + let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) + return self.account.postbox.combinedView(keys: [unreadCountsKey]) + |> map { views in + var unreadCount: Int32 = 0 + + if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { + if let count = view.count(for: .peer(peerId)) { + unreadCount = count + } + } + + return Int(unreadCount) + } + case let .replyThread(data): + let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) + return context.unreadCount + } + } public func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) { switch location { diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 09e0531d04..5483fabe3b 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -683,8 +683,8 @@ final class SharedApplicationContext { UINavigationController.attemptRotationToDeviceOrientation() }) - let accountManagerSignal = Signal { subscriber in - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false) + let accountManagerSignal = Signal, NoError> { subscriber in + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false) return (upgradedAccounts(accountManager: accountManager, rootPath: rootPath, encryptionParameters: encryptionParameters) |> deliverOnMainQueue).start(next: { progress in if self.dataImportSplash == nil { @@ -776,7 +776,7 @@ final class SharedApplicationContext { |> take(1) |> deliverOnMainQueue |> take(1) - |> mapToSignal { accountManager -> Signal<(AccountManager, InitialPresentationDataAndSettings), NoError> in + |> mapToSignal { accountManager -> Signal<(AccountManager, InitialPresentationDataAndSettings), NoError> in var systemUserInterfaceStyle: WindowUserInterfaceStyle = .light if #available(iOS 13.0, *) { if let traitCollection = window.rootViewController?.traitCollection { @@ -1192,7 +1192,7 @@ final class SharedApplicationContext { let logoutDataSignal: Signal<(AccountManager, Set), NoError> = self.sharedContextPromise.get() |> take(1) - |> mapToSignal { sharedContext -> Signal<(AccountManager, Set), NoError> in + |> mapToSignal { sharedContext -> Signal<(AccountManager, Set), NoError> in return sharedContext.sharedContext.activeAccountContexts |> map { _, accounts, _ -> Set in return Set(accounts.map { $0.1.account.peerId }) @@ -1203,7 +1203,7 @@ final class SharedApplicationContext { } return updated } - |> map { loggedOutAccountPeerIds -> (AccountManager, Set) in + |> map { loggedOutAccountPeerIds -> (AccountManager, Set) in return (sharedContext.sharedContext.accountManager, loggedOutAccountPeerIds) } } diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index fc98d9967c..8b53fab81c 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -148,7 +148,7 @@ final class AuthorizedApplicationContext { private var showCallsTabDisposable: Disposable? private var enablePostboxTransactionsDiposable: Disposable? - init(sharedApplicationContext: SharedApplicationContext, mainWindow: Window1, watchManagerArguments: Signal, context: AccountContextImpl, accountManager: AccountManager, showCallsTab: Bool, reinitializedNotificationSettings: @escaping () -> Void) { + init(sharedApplicationContext: SharedApplicationContext, mainWindow: Window1, watchManagerArguments: Signal, context: AccountContextImpl, accountManager: AccountManager, showCallsTab: Bool, reinitializedNotificationSettings: @escaping () -> Void) { self.sharedApplicationContext = sharedApplicationContext setupLegacyComponents(context: context) diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift b/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift index 13e9f7a1ea..2a0b71c431 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift @@ -386,7 +386,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail if nextType == nil { if MFMailComposeViewController.canSendMail(), let controller = controller { let formattedNumber = formatPhoneNumber(number) - strongSelf.presentEmailComposeController(address: "sms@stel.com", subject: strongSelf.presentationData.strings.Login_EmailCodeSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_EmailCodeBody(formattedNumber).string, from: controller) + strongSelf.presentEmailComposeController(address: "reports@stel.com", subject: strongSelf.presentationData.strings.Login_EmailCodeSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_EmailCodeBody(formattedNumber).string, from: controller) } else { controller?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceSplashController.swift b/submodules/TelegramUI/Sources/AuthorizationSequenceSplashController.swift index b0acbe208f..cd59666642 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceSplashController.swift +++ b/submodules/TelegramUI/Sources/AuthorizationSequenceSplashController.swift @@ -15,7 +15,7 @@ final class AuthorizationSequenceSplashController: ViewController { return self.displayNode as! AuthorizationSequenceSplashControllerNode } - private let accountManager: AccountManager + private let accountManager: AccountManager private let account: UnauthorizedAccount private let theme: PresentationTheme @@ -28,7 +28,7 @@ final class AuthorizationSequenceSplashController: ViewController { private let suggestedLocalization = Promise() private let activateLocalizationDisposable = MetaDisposable() - init(accountManager: AccountManager, account: UnauthorizedAccount, theme: PresentationTheme) { + init(accountManager: AccountManager, account: UnauthorizedAccount, theme: PresentationTheme) { self.accountManager = accountManager self.account = account self.theme = theme diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 10ac5efc37..035c3c8c3a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -214,7 +214,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G public weak var parentController: ViewController? - private let currentChatListFilter: ChatListFilterData? + private let currentChatListFilter: Int32? public var peekActions: ChatControllerPeekActions = .standard private var didSetup3dTouch: Bool = false @@ -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? @@ -318,6 +320,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var buttonKeyboardMessageDisposable: Disposable? private var cachedDataDisposable: Disposable? private var chatUnreadCountDisposable: Disposable? + private var buttonUnreadCountDisposable: Disposable? private var chatUnreadMentionCountDisposable: Disposable? private var peerInputActivitiesDisposable: Disposable? @@ -344,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? @@ -465,7 +478,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var nextChannelToReadDisposable: Disposable? - public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: ChatListFilterData? = nil) { + public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil) { let _ = ChatControllerCount.modify { value in return value + 1 } @@ -699,6 +712,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }*/ return true + case .setChatTheme: + strongSelf.presentThemeSelection() + return true default: break } @@ -1103,8 +1119,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return interfaceState }.updatedInputMode { current in - if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil { - return .media(mode: mode, expanded: nil) + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) } return current } @@ -1123,8 +1139,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in var current = current current = current.updatedInputMode { current in - if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil { - return .media(mode: mode, expanded: nil) + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) } return current } @@ -1167,8 +1183,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }.updatedInputMode { current in - if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil { - return .media(mode: mode, expanded: nil) + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) } return current } @@ -2834,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 { @@ -3314,7 +3330,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info { if strongSelf.nextChannelToReadDisposable == nil { strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(), - strongSelf.context.engine.peers.getNextUnreadChannel(peerId: channel.id, filter: strongSelf.currentChatListFilter.flatMap(chatListFilterPredicate)), + strongSelf.context.engine.peers.getNextUnreadChannel(peerId: channel.id, chatListFilterId: strongSelf.currentChatListFilter, getFilterPredicate: chatListFilterPredicate), ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager) ) |> then(.complete() |> delay(1.0, queue: .mainQueue())) @@ -3324,10 +3340,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.historyNode.offerNextChannelToRead = true - strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextPeer + strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in + return (peer: nextPeer.peer, unreadCount: nextPeer.unreadCount, location: nextPeer.location) + } strongSelf.chatDisplayNode.historyNode.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 - let nextPeerId = nextPeer?.id + let nextPeerId = nextPeer?.peer.id if strongSelf.preloadNextChatPeerId != nextPeerId { strongSelf.preloadNextChatPeerId = nextPeerId @@ -3737,13 +3755,56 @@ 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: 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 let previousChatWallpaper = strongSelf.presentationData.chatWallpaper + var themeEmoticon = themeEmoticon + if let themeEmoticonPreview = themeEmoticonPreview { + if !themeEmoticonPreview.isEmpty { + themeEmoticon = themeEmoticonPreview + } else { + themeEmoticon = nil + } + } + + 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 if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper { @@ -3885,6 +3946,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.cachedDataDisposable?.dispose() self.resolveUrlDisposable?.dispose() self.chatUnreadCountDisposable?.dispose() + self.buttonUnreadCountDisposable?.dispose() self.chatUnreadMentionCountDisposable?.dispose() self.peerInputActivitiesDisposable?.dispose() self.recentlyUsedInlineBotsDisposable?.dispose() @@ -4467,6 +4529,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 @@ -5283,7 +5347,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.commitPurposefulAction() if let forwardMessageIds = strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds { - strongSelf.forwardMessages(messageIds: forwardMessageIds, resetCurrent: true) + strongSelf.forwardMessages(messageIds: forwardMessageIds, hideSendersNames: strongSelf.presentationInterfaceState.interfaceState.forwardMessageHideSendersNames, resetCurrent: true) } } }, forwardMessages: { [weak self] messages in @@ -5292,6 +5356,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let forwardMessageIds = messages.map { $0.id }.sorted() strongSelf.forwardMessages(messageIds: forwardMessageIds) } + }, updateForwardMessageHideSendersNames: { [weak self] value in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageHideSendersNames(value) }) }) + } }, shareSelectedMessages: { [weak self] in if let strongSelf = self, let selectedIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty { strongSelf.commitPurposefulAction() @@ -6820,6 +6888,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let peerId = self.chatLocation.peerId if let subject = self.subject, case .scheduledMessages = subject { } else { + self.buttonUnreadCountDisposable = (self.context.chatLocationUnreadCount(for: self.chatLocation, contextHolder: self.chatLocationContextHolder) + |> deliverOnMainQueue).start(next: { [weak self] count in + guard let strongSelf = self else { + return + } + strongSelf.chatDisplayNode.navigateButtons.unreadCount = Int32(count) + }) + if case .peer = self.chatLocation { let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) @@ -6840,8 +6916,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount - if let view = views.views[notificationSettingsKey] as? PeerNotificationSettingsView, let notificationSettings = view.notificationSettings[peerId] { var globalRemainingUnreadChatCount = totalChatCount if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && unreadCount > 0 { @@ -7073,7 +7147,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - self.chatDisplayNode.historyNode.openNextChannelToRead = { [weak self] peer in + self.chatDisplayNode.historyNode.openNextChannelToRead = { [weak self] peer, location in guard let strongSelf = self else { return } @@ -7084,7 +7158,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G titleViewSnapshotState: strongSelf.chatTitleView?.prepareSnapshotState(), avatarSnapshotState: (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.prepareSnapshotState() ) - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), animated: false, chatListFilter: strongSelf.currentChatListFilter, completion: { nextController in + + var nextFolderId: Int32? + switch location { + case let .folder(id, _): + nextFolderId = id + case .same: + nextFolderId = strongSelf.currentChatListFilter + default: + nextFolderId = nil + } + + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), animated: false, chatListFilter: nextFolderId, completion: { nextController in (nextController as! ChatControllerImpl).animateFromPreviousController(snapshotState: snapshotState) })) } @@ -7182,7 +7267,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } - if case let .media(_, expanded) = strongSelf.presentationInterfaceState.inputMode, expanded != nil { + if case let .media(_, expanded, _) = strongSelf.presentationInterfaceState.inputMode, expanded != nil { return false } @@ -7508,7 +7593,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let avatarSnapshotState = snapshotState.avatarSnapshotState { (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.animateFromSnapshot(avatarSnapshotState) } - self.chatDisplayNode.animateFromSnapshot(snapshotState) + self.chatDisplayNode.animateFromSnapshot(snapshotState, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.chatDisplayNode.historyNode.preloadPages = true + }) + } else { + self.chatDisplayNode.historyNode.preloadPages = true } } @@ -8393,7 +8485,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 { @@ -8404,7 +8496,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) } } @@ -8767,7 +8859,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() @@ -9689,7 +9781,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private func presentPollCreation(isQuiz: Bool? = nil) { if let peer = self.presentationInterfaceState.renderedPeer?.peer { - self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, peer: peer, isQuiz: isQuiz, completion: { [weak self] message in + self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, peer: EnginePeer(peer), isQuiz: isQuiz, completion: { [weak self] message in guard let strongSelf = self else { return } @@ -10013,8 +10105,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return interfaceState } state = state.updatedInputMode { current in - if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil { - return .media(mode: mode, expanded: nil) + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) } return current } @@ -10980,16 +11072,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - private func forwardMessages(messageIds: [MessageId], resetCurrent: Bool = false) { + private func forwardMessages(messageIds: [MessageId], hideSendersNames: Bool = false, resetCurrent: Bool = false) { let _ = (self.context.account.postbox.transaction { transaction -> [Message] in return messageIds.compactMap(transaction.getMessage) } |> deliverOnMainQueue).start(next: { [weak self] messages in - self?.forwardMessages(messages: messages, resetCurrent: resetCurrent) + self?.forwardMessages(messages: messages, hideSendersNames: hideSendersNames, resetCurrent: resetCurrent) }) } - private func forwardMessages(messages: [Message], resetCurrent: Bool) { + private func forwardMessages(messages: [Message], hideSendersNames: Bool = false, resetCurrent: Bool) { let _ = self.presentVoiceMessageDiscardAlert(action: { var filter: ChatListNodePeersFilter = [.onlyWriteable, .includeSavedMessages, .excludeDisabled, .doNotSearchMessages] var hasPublicPolls = false @@ -11009,7 +11101,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var attemptSelectionImpl: ((Peer) -> Void)? let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: filter, attemptSelection: { peer in attemptSelectionImpl?(peer) - }, multipleSelection: true, forwardedMessagesCount: messages.count)) + }, multipleSelection: true, forwardedMessageIds: messages.map { $0.id })) let context = self.context attemptSelectionImpl = { [weak controller] peer in guard let controller = controller else { @@ -11024,7 +11116,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } controller.present(textAlertController(context: context, title: nil, text: presentationData.strings.Forward_ErrorDisabledForChat, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } - controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode in + controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, hideSendersNames in guard let strongSelf = self, let strongController = controller else { return } @@ -11045,8 +11137,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + var attributes: [MessageAttribute] = [] + if hideSendersNames { + attributes.append(ForwardHideSendersNamesMessageAttribute()) + } + result.append(contentsOf: messages.map { message -> EnqueueMessage in - return .forward(source: message.id, grouping: .auto, attributes: [], correlationId: nil) + return .forward(source: message.id, grouping: .auto, attributes: attributes, correlationId: nil) }) let commit: ([EnqueueMessage]) -> Void = { result in @@ -11134,7 +11231,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if resetCurrent { - strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(nil) }) }) + strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(nil).withUpdatedForwardMessageHideSendersNames(false) }) }) } var isPinnedMessages = false @@ -11285,7 +11382,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) } } @@ -11738,7 +11835,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) } } @@ -12457,7 +12554,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in return state.updatedInterfaceState { interfaceState in return interfaceState.withUpdatedEffectiveInputState(interfaceState.effectiveInputState) - }.updatedInputMode({ _ in ChatInputMode.media(mode: .other, expanded: nil) }) + }.updatedInputMode({ _ in ChatInputMode.media(mode: .other, expanded: nil, focused: false) }) }) } }), @@ -12797,6 +12894,74 @@ 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.presentCrossfadeSnapshot() + 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 be6fdc9612..8ec57a48bd 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -107,6 +107,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var titleAccessoryPanelNode: ChatTitleAccessoryPanelNode? private var inputPanelNode: ChatInputPanelNode? + private(set) var inputPanelOverscrollNode: ChatInputPanelOverscrollNode? private weak var currentDismissedInputPanelNode: ASDisplayNode? private var secondaryInputPanelNode: ChatInputPanelNode? private(set) var accessoryPanelNode: AccessoryPanelNode? @@ -246,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 @@ -819,7 +820,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } else { insets = layout.insets(options: [.input]) } - + if case .overlay = self.chatPresentationInterfaceState.mode { insets.top = 44.0 } else { @@ -987,7 +988,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let _ = accessoryPanelNode as? ReplyAccessoryPanelNode { strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedReplyMessageId(nil) }) } else if let _ = accessoryPanelNode as? ForwardAccessoryPanelNode { - strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedForwardMessageIds(nil) }) + strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedForwardMessageIds(nil).withUpdatedForwardMessageHideSendersNames(false) }) } else if let _ = accessoryPanelNode as? EditAccessoryPanelNode { strongSelf.interfaceInteraction?.setupEditMessage(nil, { _ in }) } else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode { @@ -1151,7 +1152,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var displayTopDimNode = false let ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil var expandTopDimNode = false - if case let .media(_, expanded) = self.chatPresentationInterfaceState.inputMode, expanded != nil { + if case let .media(_, expanded, _) = self.chatPresentationInterfaceState.inputMode, expanded != nil { displayTopDimNode = true expandTopDimNode = true } @@ -1229,7 +1230,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let apparentSecondaryInputPanelFrame = secondaryInputPanelFrame var apparentInputBackgroundFrame = inputBackgroundFrame var apparentNavigateButtonsFrame = navigateButtonsFrame - if case let .media(_, maybeExpanded) = self.chatPresentationInterfaceState.inputMode, let expanded = maybeExpanded, case .search = expanded, let inputPanelFrame = inputPanelFrame { + if case let .media(_, maybeExpanded, _) = self.chatPresentationInterfaceState.inputMode, let expanded = maybeExpanded, case .search = expanded, let inputPanelFrame = inputPanelFrame { let verticalOffset = -inputPanelFrame.height - 41.0 apparentInputPanelFrame = inputPanelFrame.offsetBy(dx: 0.0, dy: verticalOffset) apparentInputBackgroundFrame.size.height -= verticalOffset @@ -1853,11 +1854,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, chatLocation: self.chatPresentationInterfaceState.chatLocation, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in - if case let .media(_, expanded) = state.inputMode { + if case let .media(_, expanded, focused) = state.inputMode { if value { - return (.media(mode: .gif, expanded: expanded), nil) + return (.media(mode: .gif, expanded: expanded, focused: focused), nil) } else { - return (.media(mode: .other, expanded: expanded), nil) + return (.media(mode: .other, expanded: expanded, focused: focused), nil) } } else { return (state.inputMode, nil) @@ -2111,8 +2112,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { @objc func topDimNodeTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId { state in - if case let .media(mode, expanded) = state.inputMode, expanded != nil { - return (.media(mode: mode, expanded: nil), nil) + if case let .media(mode, expanded, focused) = state.inputMode, expanded != nil { + return (.media(mode: mode, expanded: nil, focused: focused), nil) } else { return (state.inputMode, nil) } @@ -2121,10 +2122,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } func scrollToTop() { - if case let .media(_, maybeExpanded) = self.chatPresentationInterfaceState.inputMode, maybeExpanded != nil { + if case let .media(_, maybeExpanded, _) = self.chatPresentationInterfaceState.inputMode, maybeExpanded != nil { self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId { state in - if case let .media(mode, expanded) = state.inputMode, expanded != nil { - return (.media(mode: mode, expanded: expanded), nil) + if case let .media(mode, expanded, focused) = state.inputMode, expanded != nil { + return (.media(mode: mode, expanded: expanded, focused: focused), nil) } else { return (state.inputMode, nil) } @@ -2258,7 +2259,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { |> deliverOnMainQueue).start(next: { [weak self] in self?.openStickersDisposable = nil self?.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in - return (.media(mode: .other, expanded: nil), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) + return (.media(mode: .other, expanded: nil, focused: false), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) }) }) } @@ -2351,8 +2352,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if !messages.isEmpty || self.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil { if let forwardMessageIds = self.chatPresentationInterfaceState.interfaceState.forwardMessageIds { + var attributes: [MessageAttribute] = [] + if self.chatPresentationInterfaceState.interfaceState.forwardMessageHideSendersNames { + attributes.append(ForwardHideSendersNamesMessageAttribute()) + } for id in forwardMessageIds { - messages.append(.forward(source: id, grouping: .auto, attributes: [], correlationId: nil)) + messages.append(.forward(source: id, grouping: .auto, attributes: attributes, correlationId: nil)) } } @@ -2378,7 +2383,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode { strongSelf.ignoreUpdateHeight = true textInputPanelNode.text = "" - strongSelf.requestUpdateChatInterfaceState(.immediate, true, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedComposeDisableUrlPreview(nil) }) + strongSelf.requestUpdateChatInterfaceState(.immediate, true, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardMessageHideSendersNames(false).withUpdatedComposeDisableUrlPreview(nil) }) strongSelf.ignoreUpdateHeight = false } }, usedCorrelationId) @@ -2490,6 +2495,18 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } + var shouldAllowOverscrollActions: Bool { + if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { + if inputPanelNode.isFocused { + return false + } + if !inputPanelNode.text.isEmpty { + return false + } + } + return true + } + final class SnapshotState { fileprivate let historySnapshotState: ChatHistoryListNode.SnapshotState let titleViewSnapshotState: ChatTitleView.SnapshotState? @@ -2534,8 +2551,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { ) } - func animateFromSnapshot(_ snapshotState: SnapshotState) { - self.historyNode.animateFromSnapshot(snapshotState.historySnapshotState) + func animateFromSnapshot(_ snapshotState: SnapshotState, completion: @escaping () -> Void) { + self.historyNode.animateFromSnapshot(snapshotState.historySnapshotState, completion: completion) self.navigateButtons.animateFromSnapshot(snapshotState.navigationButtonsSnapshotState) if let titleAccessoryPanelSnapshot = snapshotState.titleAccessoryPanelSnapshot { @@ -2571,4 +2588,54 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } } + + private var preivousChatInputPanelOverscrollNodeTimestamp: Double = 0.0 + + func setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode?) { + let directionUp: Bool + if let overscrollNode = overscrollNode { + if let current = self.inputPanelOverscrollNode { + directionUp = current.priority > overscrollNode.priority + } else { + directionUp = true + } + } else { + directionUp = false + } + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut) + + let timestamp = CFAbsoluteTimeGetCurrent() + if self.preivousChatInputPanelOverscrollNodeTimestamp > timestamp - 0.05 { + if let inputPanelOverscrollNode = self.inputPanelOverscrollNode { + self.inputPanelOverscrollNode = nil + inputPanelOverscrollNode.removeFromSupernode() + } + } + self.preivousChatInputPanelOverscrollNodeTimestamp = timestamp + + if let inputPanelOverscrollNode = self.inputPanelOverscrollNode { + self.inputPanelOverscrollNode = nil + inputPanelOverscrollNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: directionUp ? -5.0 : 5.0), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + inputPanelOverscrollNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak inputPanelOverscrollNode] _ in + inputPanelOverscrollNode?.removeFromSupernode() + }) + } + + if let inputPanelNode = self.inputPanelNode, let overscrollNode = overscrollNode { + self.inputPanelOverscrollNode = overscrollNode + inputPanelNode.supernode?.insertSubnode(overscrollNode, aboveSubnode: inputPanelNode) + + overscrollNode.frame = inputPanelNode.frame + overscrollNode.update(size: overscrollNode.bounds.size) + + overscrollNode.layer.animatePosition(from: CGPoint(x: 0.0, y: directionUp ? 5.0 : -5.0), to: CGPoint(), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, additive: true) + overscrollNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + + if let inputPanelNode = self.inputPanelNode { + transition.updateAlpha(node: inputPanelNode, alpha: overscrollNode == nil ? 1.0 : 0.0) + transition.updateSublayerTransformOffset(layer: inputPanelNode.layer, offset: CGPoint(x: 0.0, y: overscrollNode == nil ? 0.0 : -5.0)) + } + } } diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index bce351272f..b4c4d099d4 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -175,6 +175,10 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke }, openSettings: { }, + openTrending: { _ in + }, + dismissTrendingPacks: { _ in + }, toggleSearch: { _, _, _ in }, openPeerSpecificSettings: { @@ -342,6 +346,10 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC }, openSettings: { }, + openTrending: { _ in + }, + dismissTrendingPacks: { _ in + }, toggleSearch: { _, _, _ in }, openPeerSpecificSettings: { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index fac66dd98b..29d4f063ad 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -7,7 +7,26 @@ import AccountContext import TelegramPresentationData -func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, pendingUnpinnedAllMessages: Bool, pendingRemovedMessages: Set, associatedData: ChatMessageItemAssociatedData, updatingMedia: [MessageId: ChatUpdatingMessageMedia], customChannelDiscussionReadState: MessageId?, customThreadOutgoingReadState: MessageId?) -> [ChatHistoryEntry] { +func chatHistoryEntriesForView( + location: ChatLocation, + view: MessageHistoryView, + includeUnreadEntry: Bool, + includeEmptyEntry: Bool, + includeChatInfoEntry: Bool, + includeSearchEntry: Bool, + reverse: Bool, + groupMessages: Bool, + selectedMessages: Set?, + presentationData: ChatPresentationData, + historyAppearsCleared: Bool, + pendingUnpinnedAllMessages: Bool, + pendingRemovedMessages: Set, + associatedData: ChatMessageItemAssociatedData, + updatingMedia: [MessageId: ChatUpdatingMessageMedia], + customChannelDiscussionReadState: MessageId?, + customThreadOutgoingReadState: MessageId?, + adMessages: [Message] +) -> [ChatHistoryEntry] { if historyAppearsCleared { return [] } @@ -238,6 +257,39 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } } } + + if view.laterId == nil && !view.isLoading { + if !entries.isEmpty, case let .MessageEntry(lastMessage, _, _, _, _, _) = entries[entries.count - 1], !adMessages.isEmpty { + + var nextAdMessageId: Int32 = 1 + for message in adMessages { + let updatedMessage = Message( + stableId: UInt32.max - 1 - UInt32(nextAdMessageId), + stableVersion: message.stableVersion, + id: MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: nextAdMessageId), + globallyUniqueId: nil, + groupingKey: nil, + groupInfo: nil, + threadId: nil, + timestamp: lastMessage.timestamp, + flags: message.flags, + tags: message.tags, + globalTags: message.globalTags, + localTags: message.localTags, + forwardInfo: message.forwardInfo, + author: message.author, + text: message.text, + attributes: message.attributes, + media: message.media, + peers: message.peers, + associatedMessages: message.associatedMessages, + associatedMessageIds: message.associatedMessageIds + ) + nextAdMessageId += 1 + entries.append(.MessageEntry(updatedMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false))) + } + } + } } else if includeSearchEntry { if view.laterId == nil { if !view.entries.isEmpty { diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index ca9df5f270..364cce97a5 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -469,6 +469,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() private let messageProcessingManager = ChatMessageThrottledProcessingManager() + private let adSeenProcessingManager = ChatMessageThrottledProcessingManager() private let messageReactionsProcessingManager = ChatMessageThrottledProcessingManager() private let seenLiveLocationProcessingManager = ChatMessageThrottledProcessingManager() private let unsupportedMessageProcessingManager = ChatMessageThrottledProcessingManager() @@ -549,16 +550,19 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var isSelectionGestureEnabled = true private var overscrollView: ComponentHostView? - var nextChannelToRead: EnginePeer? + var nextChannelToRead: (peer: EnginePeer, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation)? var offerNextChannelToRead: Bool = false var nextChannelToReadDisplayName: Bool = false private var currentOverscrollExpandProgress: CGFloat = 0.0 + private var freezeOverscrollControl: Bool = false private var feedback: HapticFeedback? - var openNextChannelToRead: ((EnginePeer) -> Void)? + var openNextChannelToRead: ((EnginePeer, TelegramEngine.NextUnreadChannelLocation) -> Void)? + + private let adMessagesContext: AdMessagesHistoryContext? 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 { @@ -574,12 +578,22 @@ 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) self.prefetchManager = InChatPrefetchManager(context: context) + + let adMessages: Signal<[Message], NoError> + if case .bubbles = mode, case let .peer(peerId) = chatLocation, case .none = subject { + let adMessagesContext = context.engine.messages.adMessages(peerId: peerId) + self.adMessagesContext = adMessagesContext + adMessages = adMessagesContext.state + } else { + self.adMessagesContext = nil + adMessages = .single([]) + } let clientId = Atomic(value: nextClientId) self.clientId = clientId @@ -605,6 +619,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.messageProcessingManager.process = { [weak context] messageIds in context?.account.viewTracker.updateViewCountForMessageIds(messageIds: messageIds, clientId: clientId.with { $0 }) } + self.adSeenProcessingManager.process = { [weak self] messageIds in + guard let strongSelf = self, let adMessagesContext = strongSelf.adMessagesContext else { + return + } + for id in messageIds { + if let message = strongSelf.messageInCurrentHistoryView(id), let adAttribute = message.adAttribute { + adMessagesContext.markAsSeen(opaqueId: adAttribute.opaqueId) + } + } + } self.messageReactionsProcessingManager.process = { [weak context] messageIds in context?.account.viewTracker.updateReactionsForMessageIds(messageIds: messageIds) } @@ -628,7 +652,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } - self.preloadPages = true + self.preloadPages = false switch self.mode { case .bubbles: self.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0) @@ -845,8 +869,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, - self.currentlyPlayingMessageIdPromise.get() - ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId in + self.currentlyPlayingMessageIdPromise.get(), + adMessages + ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId, adMessages in func applyHole() { Queue.mainQueue().async { if let strongSelf = self { @@ -931,7 +956,26 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId) - let filteredEntries = chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, pendingRemovedMessages: pendingRemovedMessages, associatedData: associatedData, updatingMedia: updatingMedia, customChannelDiscussionReadState: customChannelDiscussionReadState, customThreadOutgoingReadState: customThreadOutgoingReadState) + let filteredEntries = chatHistoryEntriesForView( + location: chatLocation, + view: view, + includeUnreadEntry: mode == .bubbles, + includeEmptyEntry: mode == .bubbles && tagMask == nil, + includeChatInfoEntry: mode == .bubbles, + includeSearchEntry: includeSearchEntry && tagMask != nil, + reverse: reverse, + groupMessages: mode == .bubbles, + selectedMessages: selectedMessages, + presentationData: chatPresentationData, + historyAppearsCleared: historyAppearsCleared, + pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, + pendingRemovedMessages: pendingRemovedMessages, + associatedData: associatedData, + updatingMedia: updatingMedia, + customChannelDiscussionReadState: customChannelDiscussionReadState, + customThreadOutgoingReadState: customThreadOutgoingReadState, + adMessages: adMessages + ) let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0 let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id, locationInput: update.2) let previousValueAndVersion = previousView.swap((processedView, update.1, selectedMessages)) @@ -1087,7 +1131,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 { @@ -1166,16 +1210,17 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self?.beganDragging?() } - self.endedInteractiveDragging = { [weak self] in + self.endedInteractiveDragging = { [weak self] _ in guard let strongSelf = self else { return } - if let channel = strongSelf.nextChannelToRead, strongSelf.currentOverscrollExpandProgress >= 0.99 { - strongSelf.openNextChannelToRead?(channel) + if let nextChannelToRead = strongSelf.nextChannelToRead, strongSelf.currentOverscrollExpandProgress >= 0.99 { + strongSelf.freezeOverscrollControl = true + strongSelf.openNextChannelToRead?(nextChannelToRead.peer, nextChannelToRead.location) } } - self.didEndScrolling = { [weak self] in + self.didEndScrolling = { [weak self] _ in guard let strongSelf = self else { return } @@ -1206,7 +1251,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } private func maybeUpdateOverscrollAction(offset: CGFloat?) { - if let offset = offset, offset < 0.0, self.offerNextChannelToRead { + if self.freezeOverscrollControl { + return + } + if let offset = offset, offset < 0.0, self.offerNextChannelToRead, let chatControllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, chatControllerNode.shouldAllowOverscrollActions { let overscrollView: ComponentHostView if let current = self.overscrollView { overscrollView = current @@ -1220,24 +1268,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let expandDistance = max(-offset - 12.0, 0.0) let expandProgress: CGFloat = min(1.0, expandDistance / 90.0) - let text: String - if let nextChannelToRead = nextChannelToRead { - if self.nextChannelToReadDisplayName { - if expandProgress >= 0.99 { - //TODO:localize - text = "Release to go to \(nextChannelToRead.compactDisplayTitle)" - } else { - text = "Swipe up to go to \(nextChannelToRead.compactDisplayTitle)" - } - } else { - if expandProgress >= 0.99 { - //TODO:localize - text = "Release to go to the next unread channel" - } else { - text = "Swipe up to go to the next unread channel" - } - } - + if let _ = nextChannelToRead { let previousType = self.currentOverscrollExpandProgress >= 0.99 let currentType = expandProgress >= 0.99 @@ -1249,27 +1280,68 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } self.currentOverscrollExpandProgress = expandProgress - } else { - text = "You have no unread channels" } - let overscrollSize = overscrollView.update( + if let nextChannelToRead = self.nextChannelToRead { + let swipeText: (String, [(Int, NSRange)]) + let releaseText: (String, [(Int, NSRange)]) + switch nextChannelToRead.location { + case .same: + swipeText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress, []) + releaseText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction, []) + case .archived: + swipeText = (self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress, []) + releaseText = (self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction, []) + case .unarchived: + swipeText = (self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress, []) + releaseText = (self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction, []) + case let .folder(_, title): + swipeText = self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgress(title)._tuple + releaseText = self.currentPresentationData.strings.Chat_NextChannelFolderSwipeAction(title)._tuple + } + + if expandProgress < 0.1 { + chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil) + } else if expandProgress >= 0.99 { + if chatControllerNode.inputPanelOverscrollNode?.text.0 != releaseText.0 { + chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1)) + } + } else { + if chatControllerNode.inputPanelOverscrollNode?.text.0 != swipeText.0 { + chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2)) + } + } + } else { + chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil) + } + + let overscrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.bounds.width, height: 94.0)) + + let _ = overscrollView.update( transition: .immediate, component: AnyComponent(ChatOverscrollControl( - text: text, backgroundColor: selectDateFillStaticColor(theme: self.currentPresentationData.theme.theme, wallpaper: self.currentPresentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: self.currentPresentationData.theme.theme.chat.serviceMessage.dateTextColor, wallpaper: self.currentPresentationData.theme.wallpaper), - peer: self.nextChannelToRead, + peer: self.nextChannelToRead?.peer, + unreadCount: self.nextChannelToRead?.unreadCount ?? 0, + location: self.nextChannelToRead?.location ?? .same, context: self.context, - expandDistance: expandDistance + expandDistance: expandDistance, + absoluteRect: CGRect(origin: CGPoint(x: overscrollFrame.minX, y: self.bounds.height - overscrollFrame.minY), size: overscrollFrame.size), + absoluteSize: self.bounds.size, + wallpaperNode: chatControllerNode.backgroundNode )), environment: {}, containerSize: CGSize(width: self.bounds.width, height: 200.0) ) - overscrollView.frame = CGRect(origin: CGPoint(x: floor((self.bounds.width - overscrollSize.width) / 2.0), y: -offset + self.insets.top - overscrollSize.height - 10.0), size: overscrollSize) + overscrollView.frame = overscrollFrame } else if let overscrollView = self.overscrollView { self.overscrollView = nil overscrollView.removeFromSuperview() + + if let chatControllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode { + chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil) + } } } @@ -1305,6 +1377,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let toLaterRange = (historyView.filteredEntries.count - 1 - (visible.firstIndex - 1), historyView.filteredEntries.count - 1) var messageIdsWithViewCount: [MessageId] = [] + var messageIdsWithAds: [MessageId] = [] var messageIdsWithUpdateableReactions: [MessageId] = [] var messageIdsWithLiveLocation: [MessageId] = [] var messageIdsWithUnsupportedMedia: [MessageId] = [] @@ -1331,6 +1404,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if message.id.namespace == Namespaces.Message.Cloud { messageIdsWithViewCount.append(message.id) } + } else if attribute is AdMessageAttribute { + messageIdsWithAds.append(message.id) } else if attribute is ReplyThreadMessageAttribute { if message.id.namespace == Namespaces.Message.Cloud { messageIdsWithViewCount.append(message.id) @@ -1500,6 +1575,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if !messageIdsWithViewCount.isEmpty { self.messageProcessingManager.add(messageIdsWithViewCount) } + if !messageIdsWithAds.isEmpty { + self.adSeenProcessingManager.add(messageIdsWithAds) + } if !messageIdsWithUpdateableReactions.isEmpty { self.messageReactionsProcessingManager.add(messageIdsWithUpdateableReactions) } @@ -2463,7 +2541,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { ) } - func animateFromSnapshot(_ snapshotState: SnapshotState) { + func animateFromSnapshot(_ snapshotState: SnapshotState, completion: @escaping () -> Void) { var snapshotTopInset: CGFloat = 0.0 var snapshotBottomInset: CGFloat = 0.0 self.forEachItemNode { itemNode in @@ -2485,6 +2563,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { snapshotParentView.layer.animatePosition(from: CGPoint(x: 0.0, y: 0.0), to: CGPoint(x: 0.0, y: -self.view.bounds.height - snapshotState.snapshotBottomInset - snapshotTopInset), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak snapshotParentView] _ in snapshotParentView?.removeFromSuperview() + completion() }) self.view.layer.animatePosition(from: CGPoint(x: 0.0, y: self.view.bounds.height + snapshotTopInset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 7fd52addda..31eeb06242 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -311,9 +311,6 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte stickersEnabled = false } } -// if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, let _ = peer.botInfo { -// accessoryItems.append(.commands) -// } else if chatPresentationInterfaceState.hasBots { accessoryItems.append(.commands) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift index 4195ba9e65..0960ba0fb8 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift @@ -23,11 +23,11 @@ func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: let inputNode = ChatMediaInputNode(context: context, peerId: peerId, chatLocation: chatPresentationInterfaceState.chatLocation, controllerInteraction: controllerInteraction, chatWallpaper: chatPresentationInterfaceState.chatWallpaper, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, gifPaneIsActiveUpdated: { [weak interfaceInteraction] value in if let interfaceInteraction = interfaceInteraction { interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in - if case let .media(_, expanded) = state.inputMode { + if case let .media(_, expanded, focused) = state.inputMode { if value { - return (.media(mode: .gif, expanded: expanded), nil) + return (.media(mode: .gif, expanded: expanded, focused: focused), nil) } else { - return (.media(mode: .other, expanded: expanded), nil) + return (.media(mode: .other, expanded: expanded, focused: focused), nil) } } else { return (state.inputMode, nil) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift index d2da6abb8e..fe18abed5a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift @@ -49,10 +49,10 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS } else if let forwardMessageIds = chatPresentationInterfaceState.interfaceState.forwardMessageIds { if let forwardPanelNode = currentPanel as? ForwardAccessoryPanelNode, forwardPanelNode.messageIds == forwardMessageIds { forwardPanelNode.interfaceInteraction = interfaceInteraction - forwardPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings) + forwardPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, hideSendersNames: chatPresentationInterfaceState.interfaceState.forwardMessageHideSendersNames) return forwardPanelNode } else { - let panelNode = ForwardAccessoryPanelNode(context: context, messageIds: forwardMessageIds, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings) + let panelNode = ForwardAccessoryPanelNode(context: context, messageIds: forwardMessageIds, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, hideSendersNames: chatPresentationInterfaceState.interfaceState.forwardMessageHideSendersNames) panelNode.interfaceInteraction = interfaceInteraction return panelNode } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index fde14f3b61..10b45b366a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -288,6 +288,72 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else { return .single([]) } + + if messages.count == 1, let _ = messages[0].adAttribute { + let message = messages[0] + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + var actions: [ContextMenuItem] = [] + //TODO:localize + actions.append(.action(ContextMenuActionItem(text: "What are sponsored\nmessages?", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0)), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.default) + }))) + + actions.append(.separator) + + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + let copyTextWithEntities = { + var messageEntities: [MessageTextEntity]? + var restrictedText: String? + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + messageEntities = attribute.entities + } + if let attribute = attribute as? RestrictedContentMessageAttribute { + restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? "" + } + } + + if let restrictedText = restrictedText { + storeMessageTextInPasteboard(restrictedText, entities: nil) + } else { + storeMessageTextInPasteboard(message.text, entities: messageEntities) + } + + Queue.mainQueue().after(0.2, { + let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied) + controllerInteraction.displayUndo(content) + }) + } + + copyTextWithEntities() + f(.default) + }))) + + if let author = message.author, let addressName = author.addressName { + let link = "https://t.me/\(addressName)" + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + UIPasteboard.general.string = link + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + Queue.mainQueue().after(0.2, { + controllerInteraction.displayUndo(.linkCopied(text: presentationData.strings.Conversation_LinkCopied)) + }) + + f(.default) + }))) + } + + return .single(actions) + } var loadStickerSaveStatus: MediaId? var loadCopyMediaResource: MediaResource? @@ -615,17 +681,19 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if resourceAvailable, !message.containsSecretMedia { var mediaReference: AnyMediaReference? + var isVideo = false for media in message.media { if let image = media as? TelegramMediaImage, let _ = largestImageRepresentation(image.representations) { mediaReference = ImageMediaReference.standalone(media: image).abstract break } else if let file = media as? TelegramMediaFile, file.isVideo { mediaReference = FileMediaReference.standalone(media: file).abstract + isVideo = true break } } if let mediaReference = mediaReference { - actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Preview_SaveToCameraRoll, icon: { theme in + actions.append(.action(ContextMenuActionItem(text: isVideo ? chatPresentationInterfaceState.strings.Gallery_SaveVideo : chatPresentationInterfaceState.strings.Gallery_SaveImage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in let _ = (saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: mediaReference) diff --git a/submodules/TelegramUI/Sources/ChatLinkPreview.swift b/submodules/TelegramUI/Sources/ChatLinkPreview.swift index 45cf4ee76c..30bb9cccff 100644 --- a/submodules/TelegramUI/Sources/ChatLinkPreview.swift +++ b/submodules/TelegramUI/Sources/ChatLinkPreview.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit import TelegramNotices @@ -11,7 +12,7 @@ final class InteractiveChatLinkPreviewsResult { } } -func interactiveChatLinkPreviewsEnabled(accountManager: AccountManager, displayAlert: @escaping (InteractiveChatLinkPreviewsResult) -> Void) -> Signal { +func interactiveChatLinkPreviewsEnabled(accountManager: AccountManager, displayAlert: @escaping (InteractiveChatLinkPreviewsResult) -> Void) -> Signal { return ApplicationSpecificNotice.getSecretChatLinkPreviews(accountManager: accountManager) |> mapToSignal { value -> Signal in if let value = value { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift b/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift index 8b51713f92..2e1671d13e 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift @@ -8,6 +8,7 @@ import MergeLists enum ChatMediaInputGridEntryStableId: Equatable, Hashable { case search + case trendingList case peerSpecificSetup case sticker(ItemCollectionId, ItemCollectionItemIndex.Id) case trending(ItemCollectionId) @@ -15,6 +16,7 @@ enum ChatMediaInputGridEntryStableId: Equatable, Hashable { enum ChatMediaInputGridEntryIndex: Equatable, Comparable { case search + case trendingList case peerSpecificSetup(dismissed: Bool) case collectionIndex(ItemCollectionViewEntryIndex) case trending(ItemCollectionId, Int) @@ -23,6 +25,8 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable { switch self { case .search: return .search + case .trendingList: + return .trendingList case .peerSpecificSetup: return .peerSpecificSetup case let .collectionIndex(index): @@ -40,9 +44,16 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable { } else { return true } + case .trendingList: + switch rhs { + case .search, .trendingList: + return false + case .peerSpecificSetup, .collectionIndex, .trending: + return true + } case let .peerSpecificSetup(lhsDismissed): switch rhs { - case .search, .peerSpecificSetup: + case .search, .trendingList, .peerSpecificSetup: return false case let .collectionIndex(index): if lhsDismissed { @@ -59,7 +70,7 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable { } case let .collectionIndex(lhsIndex): switch rhs { - case .search: + case .search, .trendingList: return false case let .peerSpecificSetup(dismissed): if dismissed { @@ -74,7 +85,7 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable { } case let .trending(_, lhsIndex): switch rhs { - case .search, .peerSpecificSetup, .collectionIndex: + case .search, .trendingList, .peerSpecificSetup, .collectionIndex: return false case let .trending(_, rhsIndex): return lhsIndex < rhsIndex @@ -85,6 +96,7 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable { enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable { case search(theme: PresentationTheme, strings: PresentationStrings) + case trendingList(theme: PresentationTheme, strings: PresentationStrings, packs: [FeaturedStickerPackItem]) case peerSpecificSetup(theme: PresentationTheme, strings: PresentationStrings, dismissed: Bool) case sticker(index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, stickerPackInfo: StickerPackCollectionInfo?, canManagePeerSpecificPack: Bool?, maybeManageable: Bool, theme: PresentationTheme) case trending(TrendingPanePackEntry) @@ -93,6 +105,8 @@ enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable { switch self { case .search: return .search + case .trendingList: + return .trendingList case let .peerSpecificSetup(_, _, dismissed): return .peerSpecificSetup(dismissed: dismissed) case let .sticker(index, _, _, _, _, _): @@ -120,6 +134,26 @@ enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable { } else { return false } + case let .trendingList(lhsTheme, lhsStrings, lhsPacks): + if case let .trendingList(rhsTheme, rhsStrings, rhsPacks) = rhs { + if lhsTheme !== rhsTheme { + return false + } + if lhsStrings !== rhsStrings { + return false + } + for i in 0 ..< lhsPacks.count { + if lhsPacks[i].unread != rhsPacks[i].unread { + return false + } + if lhsPacks[i].info != rhsPacks[i].info { + return false + } + } + return true + } else { + return false + } case let .peerSpecificSetup(lhsTheme, lhsStrings, lhsDismissed): if case let .peerSpecificSetup(rhsTheme, rhsStrings, rhsDismissed) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDismissed == rhsDismissed { return true @@ -169,6 +203,10 @@ enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable { return PaneSearchBarPlaceholderItem(theme: theme, strings: strings, type: .stickers, activate: { inputNodeInteraction.toggleSearch(true, .sticker, "") }) + case let .trendingList(theme, strings, packs): + return StickerPaneTrendingListGridItem(account: account, theme: theme, strings: strings, trendingPacks: packs, inputNodeInteraction: inputNodeInteraction, dismiss: { + inputNodeInteraction.dismissTrendingPacks(packs.map { $0.info.id }) + }) case let .peerSpecificSetup(theme, strings, dismissed): return StickerPanePeerSpecificSetupGridItem(theme: theme, strings: strings, setup: { inputNodeInteraction.openPeerSpecificSettings() diff --git a/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift index cd4d66d743..f3210da8b5 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift @@ -6,6 +6,8 @@ import TelegramCore import SwiftSignalKit import Postbox import TelegramPresentationData +import AnimatedStickerNode +import TelegramAnimatedStickerNode enum ChatMediaInputMetaSectionItemType: Equatable { case savedStickers @@ -13,10 +15,11 @@ enum ChatMediaInputMetaSectionItemType: Equatable { case stickersMode case savedGifs case trendingGifs - case gifEmoji(String) + case gifEmoji(String, TelegramMediaFile?) } final class ChatMediaInputMetaSectionItem: ListViewItem { + let account: Account let inputNodeInteraction: ChatMediaInputNodeInteraction let type: ChatMediaInputMetaSectionItemType let theme: PresentationTheme @@ -27,7 +30,8 @@ final class ChatMediaInputMetaSectionItem: ListViewItem { return true } - init(inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) { + init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, expanded: Bool, selected: @escaping () -> Void) { + self.account = account self.inputNodeInteraction = inputNodeInteraction self.type = type self.selectedItem = selected @@ -41,7 +45,7 @@ final class ChatMediaInputMetaSectionItem: ListViewItem { Queue.mainQueue().async { node.inputNodeInteraction = self.inputNodeInteraction node.setItem(item: self) - node.updateTheme(theme: self.theme, expanded: self.expanded) + node.updateTheme(account: self.account, theme: self.theme, expanded: self.expanded) node.updateIsHighlighted() node.updateAppearanceTransition(transition: .immediate) @@ -61,7 +65,7 @@ final class ChatMediaInputMetaSectionItem: ListViewItem { Queue.mainQueue().async { completion(ListViewItemNodeLayout(contentSize: self.expanded ? expandedBoundingSize : boundingSize, insets: node().insets), { _ in (node() as? ChatMediaInputMetaSectionItemNode)?.setItem(item: self) - (node() as? ChatMediaInputMetaSectionItemNode)?.updateTheme(theme: self.theme, expanded: self.expanded) + (node() as? ChatMediaInputMetaSectionItemNode)?.updateTheme(account: self.account, theme: self.theme, expanded: self.expanded) }) } } @@ -86,6 +90,8 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { private let highlightNode: ASImageNode private let titleNode: ImmediateTextNode + private var animatedStickerNode: AnimatedStickerNode? + private var currentExpanded = false var item: ChatMediaInputMetaSectionItem? @@ -94,6 +100,23 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { var theme: PresentationTheme? + override var visibility: ListViewItemNodeVisibility { + didSet { + self.visibilityStatus = self.visibility != .none + } + } + + private var visibilityStatus: Bool = false { + didSet { + if self.visibilityStatus != oldValue { + let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false + self.animatedStickerNode?.visibility = self.visibilityStatus && loopAnimatedStickers + } + } + } + + private let stickerFetchedDisposable = MetaDisposable() + init() { self.containerNode = ASDisplayNode() self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) @@ -130,6 +153,10 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { self.scalingNode.addSubnode(self.textNodeContainer) } + deinit { + self.stickerFetchedDisposable.dispose() + } + override func didLoad() { super.didLoad() } @@ -146,7 +173,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { } } - func updateTheme(theme: PresentationTheme, expanded: Bool) { + func updateTheme(account: Account, theme: PresentationTheme, expanded: Bool) { let imageSize = CGSize(width: 26.0 * 1.6, height: 26.0 * 1.6) self.imageNode.frame = CGRect(origin: CGPoint(x: floor((expandedBoundingSize.width - imageSize.width) / 2.0), y: floor((expandedBoundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize) @@ -174,7 +201,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { case .trendingGifs: self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme) title = "Trending" - case let .gifEmoji(emoji): + case let .gifEmoji(emoji, file): var emoji = emoji switch emoji { case "😡": @@ -200,16 +227,38 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { default: break } - if emoji == "🥳" { - if #available(iOSApplicationExtension 12.1, iOS 12.1, *) { - } else { - emoji = "🎉" - } - } +// if emoji == "🥳" { +// if #available(iOSApplicationExtension 12.1, iOS 12.1, *) { +// } else { +// emoji = "🎉" +// } +// } self.imageNode.image = nil - self.textNode.attributedText = NSAttributedString(string: emoji, font: Font.regular(43.0), textColor: .black) - let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) - self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.textNodeContainer.bounds.width - textSize.width) / 2.0), y: floor((self.textNodeContainer.bounds.height - textSize.height) / 2.0)), size: textSize) + + if let file = file { + + let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false + let animatedStickerNode: AnimatedStickerNode + if let current = self.animatedStickerNode { + animatedStickerNode = current + } else { + animatedStickerNode = AnimatedStickerNode() + self.animatedStickerNode = animatedStickerNode + // if let placeholderNode = self.placeholderNode { + // self.scalingNode.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode) + // } else { + self.scalingNode.addSubnode(animatedStickerNode) + // } + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: file.resource), width: 128, height: 128, mode: .cached) + } + animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers + + self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start()) + } else { + self.textNode.attributedText = NSAttributedString(string: emoji, font: Font.regular(43.0), textColor: .black) + let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) + self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.textNodeContainer.bounds.width - textSize.width) / 2.0), y: floor((self.textNodeContainer.bounds.height - textSize.height) / 2.0)), size: textSize) + } } } self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(11.0), textColor: theme.chat.inputPanel.primaryTextColor) @@ -222,18 +271,25 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale) - expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0))) + expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0))) - expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0) - let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height)) + let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width, height: expandedBoundingSize.height)) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize) let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size) expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) + let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate + alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) + self.currentExpanded = expanded + if let animatedStickerNode = self.animatedStickerNode { + animatedStickerNode.frame = self.imageNode.frame + animatedStickerNode.updateLayout(size: self.imageNode.frame.size) + } + expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize)) } @@ -254,7 +310,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode { if case .trending = inputNodeInteraction.highlightedGifMode { isHighlighted = true } - case let .gifEmoji(emoji): + case let .gifEmoji(emoji, _): if case .emojiSearch(emoji) = inputNodeInteraction.highlightedGifMode { isHighlighted = true } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index c7e4cf9cd5..fea4e9e675 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -7,6 +7,7 @@ import TelegramCore import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences +import TelegramNotices import MergeLists import AccountContext import StickerPackPreviewUI @@ -29,11 +30,20 @@ enum CanInstallPeerSpecificPack { case available(peer: Peer, dismissed: Bool) } +final class ChatMediaInputPanelOpaqueState { + let entries: [ChatMediaInputPanelEntry] + + init(entries: [ChatMediaInputPanelEntry]) { + self.entries = entries + } +} + struct ChatMediaInputPanelTransition { let deletions: [ListViewDeleteItem] let insertions: [ListViewInsertItem] let updates: [ListViewUpdateItem] let scrollToItem: ListViewScrollToItem? + let updateOpaqueState: ChatMediaInputPanelOpaqueState? } struct ChatMediaInputGridTransition { @@ -54,7 +64,7 @@ func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fr let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, inputNodeInteraction: inputNodeInteraction), directionHint: nil) } - return ChatMediaInputPanelTransition(deletions: deletions, insertions: insertions, updates: updates, scrollToItem: scrollToItem) + return ChatMediaInputPanelTransition(deletions: deletions, insertions: insertions, updates: updates, scrollToItem: scrollToItem, updateOpaqueState: ChatMediaInputPanelOpaqueState(entries: toEntries)) } func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition { @@ -67,7 +77,7 @@ func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemColle switch toEntries[i] { case .search, .peerSpecificSetup, .trending: break - case .sticker: + case .trendingList, .sticker: scrollToItem = GridNodeScrollToItem(index: i, position: .top(0.0), transition: .immediate, directionHint: .down, adjustForSection: true, adjustForTopInset: true) } } @@ -140,7 +150,7 @@ func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemColle var firstIndexInSectionOffset = 0 if !toEntries.isEmpty { switch toEntries[0].index { - case .search, .peerSpecificSetup, .trending: + case .search, .trendingList, .peerSpecificSetup, .trending: break case let .collectionIndex(index): firstIndexInSectionOffset = Int(index.itemIndex.index) @@ -152,13 +162,13 @@ func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemColle return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated) } -func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasUnreadTrending: Bool?, theme: PresentationTheme, hasGifs: Bool = true, hasSettings: Bool = true, expanded: Bool = false) -> [ChatMediaInputPanelEntry] { +func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, temporaryPackOrder: [ItemCollectionId]? = nil, trendingIsDismissed: Bool = false, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, theme: PresentationTheme, hasGifs: Bool = true, hasSettings: Bool = true, expanded: Bool = false) -> [ChatMediaInputPanelEntry] { var entries: [ChatMediaInputPanelEntry] = [] if hasGifs { entries.append(.recentGifs(theme, expanded)) } - if let hasUnreadTrending = hasUnreadTrending { - entries.append(.trending(hasUnreadTrending, theme, expanded)) + if trendingIsDismissed { + entries.append(.trending(true, theme, expanded)) } if let savedStickers = savedStickers, !savedStickers.items.isEmpty { entries.append(.savedStickers(theme, expanded)) @@ -191,13 +201,36 @@ func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: Ordere entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded)) } var index = 0 - for (_, info, item) in view.collectionInfos { - if let info = info as? StickerPackCollectionInfo, item != nil { - entries.append(.stickerPack(index: index, info: info, topItem: item as? StickerPackItem, theme: theme, expanded: expanded)) - index += 1 + + var sortedPacks: [(ItemCollectionId, StickerPackCollectionInfo, StickerPackItem?)] = [] + for (id, info, item) in view.collectionInfos { + if let info = info as? StickerPackCollectionInfo, let item = item as? StickerPackItem { + sortedPacks.append((id, info, item)) } } + if let temporaryPackOrder = temporaryPackOrder { + var packDict: [ItemCollectionId: Int] = [:] + for i in 0 ..< sortedPacks.count { + packDict[sortedPacks[i].0] = i + } + var tempSortedPacks: [(ItemCollectionId, StickerPackCollectionInfo, StickerPackItem?)] = [] + var processedPacks = Set() + for id in temporaryPackOrder { + if let index = packDict[id] { + tempSortedPacks.append(sortedPacks[index]) + processedPacks.insert(id) + } + } + let restPacks = sortedPacks.filter { !processedPacks.contains($0.0) } + sortedPacks = restPacks + tempSortedPacks + } + + for (_, info, topItem) in sortedPacks { + entries.append(.stickerPack(index: index, info: info, topItem: topItem, theme: theme, expanded: expanded)) + index += 1 + } + if peerSpecificPack == nil, case let .available(peer, true) = canInstallPeerSpecificPack { entries.append(.peerSpecific(theme: theme, peer: peer, expanded: expanded)) } @@ -208,20 +241,20 @@ func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: Ordere return entries } -func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [String], expanded: Bool) -> [ChatMediaInputPanelEntry] { +func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [String], animatedEmojiStickers: [String: [StickerPackItem]], expanded: Bool) -> [ChatMediaInputPanelEntry] { var entries: [ChatMediaInputPanelEntry] = [] entries.append(.stickersMode(theme, expanded)) entries.append(.savedGifs(theme, expanded)) entries.append(.trendingGifs(theme, expanded)) for reaction in reactions { - entries.append(.gifEmotion(entries.count, theme, reaction, expanded)) + entries.append(.gifEmotion(entries.count, theme, reaction, animatedEmojiStickers[reaction]?.first?.file, expanded)) } return entries } -func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasSearch: Bool = true, hasAccessories: Bool = true, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] { +func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, trendingPacks: [FeaturedStickerPackItem], trendingIsDismissed: Bool = false, hasSearch: Bool = true, hasAccessories: Bool = true, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] { var entries: [ChatMediaInputGridEntry] = [] if hasSearch && view.lower == nil { @@ -249,6 +282,10 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered } } + if !trendingIsDismissed { + entries.append(.trendingList(theme: theme, strings: strings, packs: trendingPacks)) + } + if let recentStickers = recentStickers, !recentStickers.items.isEmpty { let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FrequentlyUsed.uppercased(), shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0) var addedCount = 0 @@ -346,6 +383,8 @@ final class ChatMediaInputNodeInteraction { let navigateBackToStickers: () -> Void let setGifMode: (ChatMediaInputGifMode) -> Void let openSettings: () -> Void + let openTrending: (ItemCollectionId?) -> Void + let dismissTrendingPacks: ([ItemCollectionId]) -> Void let toggleSearch: (Bool, ChatMediaInputSearchMode?, String) -> Void let openPeerSpecificSettings: () -> Void let dismissPeerSpecificSettings: () -> Void @@ -360,11 +399,13 @@ final class ChatMediaInputNodeInteraction { var displayStickerPlaceholder = true var displayStickerPackManageControls = true - init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) { + init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, openTrending: @escaping (ItemCollectionId?) -> Void, dismissTrendingPacks: @escaping ([ItemCollectionId]) -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) { self.navigateToCollectionId = navigateToCollectionId self.navigateBackToStickers = navigateBackToStickers self.setGifMode = setGifMode self.openSettings = openSettings + self.openTrending = openTrending + self.dismissTrendingPacks = dismissTrendingPacks self.toggleSearch = toggleSearch self.openPeerSpecificSettings = openPeerSpecificSettings self.dismissPeerSpecificSettings = dismissPeerSpecificSettings @@ -451,14 +492,16 @@ final class ChatMediaInputNode: ChatInputNode { private var currentView: ItemCollectionsView? private let dismissedPeerSpecificStickerPack = Promise() - private var panelCollapseScrollToIndex: Int? - private let panelExpandedPromise = ValuePromise(false) - private var panelExpanded: Bool = false { + private var panelFocusScrollToIndex: Int? + private var panelFocusInitialPosition: CGPoint? + private let panelIsFocusedPromise = ValuePromise(false) + private var panelIsFocused: Bool = false { didSet { - self.panelExpandedPromise.set(self.panelExpanded) + self.panelIsFocusedPromise.set(self.panelIsFocused) } } - private var panelCollapseTimer: SwiftSignalKit.Timer? + private var panelFocusTimer: SwiftSignalKit.Timer? + private var lastReorderItemIndex: Int? var requestDisableStickerAnimations: ((Bool) -> Void)? @@ -494,27 +537,26 @@ final class ChatMediaInputNode: ChatInputNode { self.themeAndStringsPromise = Promise((theme, strings)) self.collectionListPanel = ASDisplayNode() - self.collectionListPanel.clipsToBounds = true self.collectionListSeparator = ASDisplayNode() self.collectionListSeparator.isLayerBacked = true self.collectionListSeparator.backgroundColor = theme.chat.inputMediaPanel.panelSeparatorColor self.collectionListContainer = CollectionListContainerNode() - self.collectionListContainer.clipsToBounds = true self.listView = ListView() -// self.listView.clipsToBounds = false + self.listView.useSingleDimensionTouchPoint = true + self.listView.reorderedItemHasShadow = false self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) - self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false + self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = true self.listView.accessibilityPageScrolledString = { row, count in return strings.VoiceOver_ScrollStatus(row, count).string } self.gifListView = ListView() -// self.gifListView.clipsToBounds = false + self.gifListView.useSingleDimensionTouchPoint = true self.gifListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) - self.gifListView.scroller.panGestureRecognizer.cancelsTouchesInView = false + self.gifListView.scroller.panGestureRecognizer.cancelsTouchesInView = true self.gifListView.accessibilityPageScrolledString = { row, count in return strings.VoiceOver_ScrollStatus(row, count).string } @@ -542,6 +584,147 @@ final class ChatMediaInputNode: ChatInputNode { super.init() + let temporaryPackOrder = Promise<[ItemCollectionId]?>(nil) + + self.listView.willBeginReorder = { [weak self] point in + self?.listView.beganInteractiveDragging(point) + } + + self.listView.reorderBegan = { [weak self] in + self?.stopCollapseTimer() + } + + self.listView.reorderItem = { [weak self] fromIndex, toIndex, opaqueState in + guard let entries = (opaqueState as? ChatMediaInputPanelOpaqueState)?.entries else { + return .single(false) + } + self?.lastReorderItemIndex = toIndex + + let fromEntry = entries[fromIndex] + guard case let .stickerPack(_, fromPackInfo, _, _, _) = fromEntry else { + return .single(false) + } + var referenceId: ItemCollectionId? + var beforeAll = false + var afterAll = false + if toIndex < entries.count { + switch entries[toIndex] { + case let .stickerPack(_, toPackInfo, _, _, _): + referenceId = toPackInfo.id + default: + if entries[toIndex] < fromEntry { + beforeAll = true + } else { + afterAll = true + } + } + } else { + afterAll = true + } + + var currentIds: [ItemCollectionId] = [] + for entry in entries { + switch entry { + case let .stickerPack(_, info, _, _, _): + currentIds.append(info.id) + default: + break + } + } + + var previousIndex: Int? + for i in 0 ..< currentIds.count { + if currentIds[i] == fromPackInfo.id { + previousIndex = i + currentIds.remove(at: i) + break + } + } + + var didReorder = false + + if let referenceId = referenceId { + var inserted = false + for i in 0 ..< currentIds.count { + if currentIds[i] == referenceId { + if fromIndex < toIndex { + didReorder = previousIndex != i + 1 + currentIds.insert(fromPackInfo.id, at: i + 1) + } else { + didReorder = previousIndex != i + currentIds.insert(fromPackInfo.id, at: i) + } + inserted = true + break + } + } + if !inserted { + didReorder = previousIndex != currentIds.count + currentIds.append(fromPackInfo.id) + } + } else if beforeAll { + didReorder = previousIndex != 0 + currentIds.insert(fromPackInfo.id, at: 0) + } else if afterAll { + didReorder = previousIndex != currentIds.count + currentIds.append(fromPackInfo.id) + } + + temporaryPackOrder.set(.single(currentIds)) + + return .single(didReorder) + } + self.listView.reorderCompleted = { [weak self] opaqueState in + guard let entries = (opaqueState as? ChatMediaInputPanelOpaqueState)?.entries else { + return + } + + var currentIds: [ItemCollectionId] = [] + for entry in entries { + switch entry { + case let .stickerPack(_, info, _, _, _): + currentIds.append(info.id) + default: + break + } + } + let _ = (context.account.postbox.transaction { transaction -> Void in + let namespace = Namespaces.ItemCollection.CloudStickerPacks + let infos = transaction.getItemCollectionsInfos(namespace: namespace) + + var packDict: [ItemCollectionId: Int] = [:] + for i in 0 ..< infos.count { + packDict[infos[i].0] = i + } + var tempSortedPacks: [(ItemCollectionId, ItemCollectionInfo)] = [] + var processedPacks = Set() + for id in currentIds { + if let index = packDict[id] { + tempSortedPacks.append(infos[index]) + processedPacks.insert(id) + } + } + let restPacks = infos.filter { !processedPacks.contains($0.0) } + let sortedPacks = restPacks + tempSortedPacks + addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: namespace, content: .sync, noDelay: false) + transaction.replaceItemCollectionInfos(namespace: namespace, itemCollectionInfos: sortedPacks) + } + |> deliverOnMainQueue).start(completed: { [weak self] in + temporaryPackOrder.set(.single(nil)) + + if let strongSelf = self { + if let lastReorderItemIndex = strongSelf.lastReorderItemIndex { + strongSelf.lastReorderItemIndex = nil + if strongSelf.panelIsFocused { + strongSelf.panelFocusScrollToIndex = lastReorderItemIndex + } + } + } + + self?.startCollapseTimer(timeout: 1.0) + }) + } + self.inputNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in if let strongSelf = self, let currentView = strongSelf.currentView, (collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId || true) { var index: Int32 = 0 @@ -550,6 +733,7 @@ final class ChatMediaInputNode: ChatInputNode { } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue { strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( context: strongSelf.context, + highlightedPackId: nil, sendSticker: { fileReference, sourceNode, sourceRect in if let strongSelf = self { @@ -609,6 +793,23 @@ final class ChatMediaInputNode: ChatInputNode { controller.navigationPresentation = .modal strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) } + }, openTrending: { [weak self] packId in + if let strongSelf = self { + strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( + context: strongSelf.context, + highlightedPackId: packId, + sendSticker: { + fileReference, sourceNode, sourceRect in + if let strongSelf = self { + return strongSelf.controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect) + } else { + return false + } + } + )) + } + }, dismissTrendingPacks: { packIds in + let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: packIds.map { $0.id }).start() }, toggleSearch: { [weak self] value, searchMode, query in if let strongSelf = self { if let searchMode = searchMode, value { @@ -636,8 +837,8 @@ final class ChatMediaInputNode: ChatInputNode { if let strongSelf = self { strongSelf.controllerInteraction.updateInputMode { current in switch current { - case let .media(mode, _): - return .media(mode: mode, expanded: .search(searchMode)) + case let .media(mode, _, focused): + return .media(mode: mode, expanded: .search(searchMode), focused: focused) default: return current } @@ -648,8 +849,8 @@ final class ChatMediaInputNode: ChatInputNode { } else { strongSelf.controllerInteraction.updateInputMode { current in switch current { - case let .media(mode, _): - return .media(mode: mode, expanded: nil) + case let .media(mode, _, focused): + return .media(mode: mode, expanded: nil, focused: focused) default: return current } @@ -858,10 +1059,30 @@ final class ChatMediaInputNode: ChatInputNode { } |> distinctUntilChanged + let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false) + |> map { animatedEmoji -> [String: [StickerPackItem]] in + var animatedEmojiStickers: [String: [StickerPackItem]] = [:] + switch animatedEmoji { + case let .result(_, items, _): + for case let item as StickerPackItem in items { + if let emoji = item.getStringRepresentationsOfIndexKeys().first { + animatedEmojiStickers[emoji.basicEmoji.0] = [item] + let strippedEmoji = emoji.basicEmoji.0.strippedEmoji + if animatedEmojiStickers[strippedEmoji] == nil { + animatedEmojiStickers[strippedEmoji] = [item] + } + } + } + default: + break + } + return animatedEmojiStickers + } + let previousView = Atomic(value: nil) let transitionQueue = Queue() - let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelExpandedPromise.get()) - |> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in + let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelIsFocusedPromise.get(), ApplicationSpecificNotice.dismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager), temporaryPackOrder.get(), animatedEmojiStickers) + |> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded, dismissedTrendingStickerPacks, temporaryPackOrder, animatedEmojiStickers -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in let (view, viewUpdate) = viewAndUpdate let previous = previousView.swap(view) var update = viewUpdate @@ -885,20 +1106,14 @@ final class ChatMediaInputNode: ChatInputNode { installedPacks.insert(info.0) } - var hasUnreadTrending: Bool? - for pack in trendingPacks { - if hasUnreadTrending == nil { - hasUnreadTrending = false - } - if pack.unread { - hasUnreadTrending = true - break - } + var trendingIsDismissed = false + if let dismissedTrendingStickerPacks = dismissedTrendingStickerPacks, Set(trendingPacks.map({ $0.info.id.id })) == Set(dismissedTrendingStickerPacks) { + trendingIsDismissed = true } - - let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, hasUnreadTrending: hasUnreadTrending, theme: theme, expanded: panelExpanded) - let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions, expanded: panelExpanded) - var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme) + + let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, temporaryPackOrder: temporaryPackOrder, trendingIsDismissed: trendingIsDismissed, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme, expanded: panelExpanded) + let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions, animatedEmojiStickers: animatedEmojiStickers, expanded: panelExpanded) + var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, trendingPacks: trendingPacks, trendingIsDismissed: trendingIsDismissed, strings: strings, theme: theme) if view.higher == nil { var hasTopSeparator = true @@ -943,7 +1158,9 @@ final class ChatMediaInputNode: ChatInputNode { if let topVisibleSection = visibleItems.topSectionVisible as? ChatMediaInputStickerGridSection { topVisibleCollectionId = topVisibleSection.collectionId } else if let topVisible = visibleItems.topVisible { - if let item = topVisible.1 as? ChatMediaInputStickerGridItem { + if let _ = topVisible.1 as? StickerPaneTrendingListGridItem { + topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0) + } else if let item = topVisible.1 as? ChatMediaInputStickerGridItem { topVisibleCollectionId = item.index.collectionId } else if let _ = topVisible.1 as? StickerPanePeerSpecificSetupGridItem { topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0) @@ -998,32 +1215,80 @@ final class ChatMediaInputNode: ChatInputNode { } self.listView.beganInteractiveDragging = { [weak self] position in - if let strongSelf = self, false { - if !strongSelf.panelExpanded, let index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) { - strongSelf.panelCollapseScrollToIndex = index + if let strongSelf = self { + strongSelf.stopCollapseTimer() + + var position = position + var index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) + if index == nil { + position.y += 10.0 + index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) + } + if let index = index { + strongSelf.panelFocusScrollToIndex = index + strongSelf.panelFocusInitialPosition = position + } + strongSelf.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: true)) + } else { + return (inputTextState, inputMode) + } } - strongSelf.updateIsExpanded(true) } } - self.listView.didEndScrolling = { [weak self] in - if let strongSelf = self, false { - strongSelf.setupCollapseTimer() + self.listView.endedInteractiveDragging = { [weak self] position in + if let strongSelf = self { + strongSelf.panelFocusInitialPosition = position + } + } + + self.listView.didEndScrolling = { [weak self] decelerated in + if let strongSelf = self { + if decelerated { + strongSelf.panelFocusScrollToIndex = nil + strongSelf.panelFocusInitialPosition = nil + } + strongSelf.startCollapseTimer(timeout: decelerated ? 0.5 : 1.5) } } self.gifListView.beganInteractiveDragging = { [weak self] position in - if let strongSelf = self, false { - if !strongSelf.panelExpanded, let index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) { - strongSelf.panelCollapseScrollToIndex = index + if let strongSelf = self { + strongSelf.stopCollapseTimer() + var position = position + var index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) + if index == nil { + position.y += 10.0 + index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) + } + if let index = index { + strongSelf.panelFocusScrollToIndex = index + } + strongSelf.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: true)) + } else { + return (inputTextState, inputMode) + } } - strongSelf.updateIsExpanded(true) } } - self.gifListView.didEndScrolling = { [weak self] in - if let strongSelf = self, false { - strongSelf.setupCollapseTimer() + self.gifListView.endedInteractiveDragging = { [weak self] position in + if let strongSelf = self { + strongSelf.panelFocusInitialPosition = position + } + } + + self.gifListView.didEndScrolling = { [weak self] decelerated in + if let strongSelf = self { + if decelerated { + strongSelf.panelFocusScrollToIndex = nil + strongSelf.panelFocusInitialPosition = nil + } + strongSelf.startCollapseTimer(timeout: decelerated ? 0.5 : 1.5) } } } @@ -1031,26 +1296,39 @@ final class ChatMediaInputNode: ChatInputNode { deinit { self.disposable.dispose() self.searchContainerNodeLoadedDisposable.dispose() - self.panelCollapseTimer?.invalidate() + self.panelFocusTimer?.invalidate() } private func updateIsExpanded(_ isExpanded: Bool) { - self.panelCollapseTimer?.invalidate() - - self.panelExpanded = isExpanded + guard self.panelIsFocused != isExpanded else { + return + } + + self.panelIsFocused = isExpanded self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: self.currentCollectionListPanelOffset(), transition: .animated(duration: 0.3, curve: .spring)) } - private func setupCollapseTimer() { - self.panelCollapseTimer?.invalidate() + private func startCollapseTimer(timeout: Double) { + self.panelFocusTimer?.invalidate() - let timer = SwiftSignalKit.Timer(timeout: 1.5, repeat: false, completion: { [weak self] in - self?.updateIsExpanded(false) + let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { [weak self] in + self?.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) + } else { + return (inputTextState, inputMode) + } + } }, queue: Queue.mainQueue()) - self.panelCollapseTimer = timer + self.panelFocusTimer = timer timer.start() } + private func stopCollapseTimer() { + self.panelFocusTimer?.invalidate() + self.panelFocusTimer = nil + } + private func openGifContextMenu(file: MultiplexedVideoNodeFile, sourceNode: ASDisplayNode, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) { let canSaveGif: Bool if file.file.media.fileId.namespace == Namespaces.Media.CloudFile { @@ -1502,10 +1780,19 @@ final class ChatMediaInputNode: ChatInputNode { } itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelCollapseScrollToIndex = targetIndex - self.updateIsExpanded(false) + if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelFocusScrollToIndex = targetIndex + self.panelFocusInitialPosition = nil + self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) + } else { + return (inputTextState, inputMode) + } + } } else { + self.panelFocusScrollToIndex = nil + self.panelFocusInitialPosition = nil self.listView.ensureItemNodeVisible(itemNode) } ensuredNodeVisible = true @@ -1513,10 +1800,19 @@ final class ChatMediaInputNode: ChatInputNode { } else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode { itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelCollapseScrollToIndex = targetIndex - self.updateIsExpanded(false) + if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelFocusScrollToIndex = targetIndex + self.panelFocusInitialPosition = nil + self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) + } else { + return (inputTextState, inputMode) + } + } } else { + self.panelFocusScrollToIndex = nil + self.panelFocusInitialPosition = nil self.listView.ensureItemNodeVisible(itemNode) } ensuredNodeVisible = true @@ -1524,10 +1820,19 @@ final class ChatMediaInputNode: ChatInputNode { } else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode { itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelCollapseScrollToIndex = targetIndex - self.updateIsExpanded(false) + if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelFocusScrollToIndex = targetIndex + self.panelFocusInitialPosition = nil + self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) + } else { + return (inputTextState, inputMode) + } + } } else { + self.panelFocusScrollToIndex = nil + self.panelFocusInitialPosition = nil self.listView.ensureItemNodeVisible(itemNode) } ensuredNodeVisible = true @@ -1535,10 +1840,19 @@ final class ChatMediaInputNode: ChatInputNode { } else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode { itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelCollapseScrollToIndex = targetIndex - self.updateIsExpanded(false) + if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelFocusScrollToIndex = targetIndex + self.panelFocusInitialPosition = nil + self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) + } else { + return (inputTextState, inputMode) + } + } } else { + self.panelFocusScrollToIndex = nil + self.panelFocusInitialPosition = nil self.listView.ensureItemNodeVisible(itemNode) } ensuredNodeVisible = true @@ -1546,10 +1860,19 @@ final class ChatMediaInputNode: ChatInputNode { } else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode { itemNode.updateIsHighlighted() if itemNode.currentCollectionId == collectionId { - if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) { - self.panelCollapseScrollToIndex = targetIndex - self.updateIsExpanded(false) + if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) { + self.panelFocusScrollToIndex = targetIndex + self.panelFocusInitialPosition = nil + self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) + } else { + return (inputTextState, inputMode) + } + } } else { + self.panelFocusScrollToIndex = nil + self.panelFocusInitialPosition = nil self.listView.ensureItemNodeVisible(itemNode) } ensuredNodeVisible = true @@ -1562,10 +1885,19 @@ final class ChatMediaInputNode: ChatInputNode { let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId }) if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex { let toRight = targetIndex > firstVisibleIndex - if self.panelExpanded { - self.panelCollapseScrollToIndex = targetIndex - self.updateIsExpanded(false) + if self.panelIsFocused { + self.panelFocusScrollToIndex = targetIndex + self.panelFocusInitialPosition = nil + self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in + if case let .media(mode, expanded, _) = inputMode { + return (inputTextState, .media(mode: mode, expanded: expanded, focused: false)) + } else { + return (inputTextState, inputMode) + } + } } else { + self.panelFocusScrollToIndex = nil + self.panelFocusInitialPosition = nil self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil) } } @@ -1643,7 +1975,7 @@ final class ChatMediaInputNode: ChatInputNode { override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) { var searchMode: ChatMediaInputSearchMode? - if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = self.validLayout, case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded { + if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = self.validLayout, case let .media(_, maybeExpanded, _) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded { searchMode = mode } @@ -1659,8 +1991,12 @@ final class ChatMediaInputNode: ChatInputNode { let separatorHeight = UIScreenPixel let panelHeight: CGFloat + var isFocused = false var isExpanded: Bool = false - if case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded { + if case let .media(_, _, focused) = interfaceState.inputMode { + isFocused = focused + } + if case let .media(_, maybeExpanded, _) = interfaceState.inputMode, let expanded = maybeExpanded { isExpanded = true switch expanded { case .content: @@ -1677,6 +2013,8 @@ final class ChatMediaInputNode: ChatInputNode { panelHeight = standardInputHeight } + self.updateIsExpanded(isFocused) + if displaySearch { if let searchContainerNode = self.searchContainerNode { let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight)) @@ -1721,10 +2059,10 @@ final class ChatMediaInputNode: ChatInputNode { transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: CGSize(width: width, height: 41.0))) transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: CGSize(width: width, height: separatorHeight))) - self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 20.0, height: width) + self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 40.0, height: width) transition.updatePosition(node: self.listView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0)) - self.gifListView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 20.0, height: width) + self.gifListView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 40.0, height: width) transition.updatePosition(node: self.gifListView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0)) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) @@ -1891,18 +2229,27 @@ final class ChatMediaInputNode: ChatInputNode { } var scrollToItem: ListViewScrollToItem? - if let targetIndex = self.panelCollapseScrollToIndex { + if let targetIndex = self.panelFocusScrollToIndex, !self.listView.isReordering { var position: ListViewScrollPosition - if self.panelExpanded { - position = .center(.top) + if self.panelIsFocused { + if let initialPosition = self.panelFocusInitialPosition { + position = .top(96.0 + (initialPosition.y - self.listView.frame.height / 2.0) * 0.5) + } else { + position = .top(96.0) + } } else { - position = .top(self.listView.frame.height / 2.0 + 96.0) + if let initialPosition = self.panelFocusInitialPosition { + position = .top(self.listView.frame.height / 2.0 + 96.0 + (initialPosition.y - self.listView.frame.height / 2.0)) + } else { + position = .top(self.listView.frame.height / 2.0 + 96.0) + } + self.panelFocusScrollToIndex = nil + self.panelFocusInitialPosition = nil } - scrollToItem = ListViewScrollToItem(index: targetIndex, position: position, animated: true, curve: .Default(duration: nil), directionHint: .Down) - self.panelCollapseScrollToIndex = nil + scrollToItem = ListViewScrollToItem(index: targetIndex, position: position, animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) } - self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateOpaqueState: nil, completion: { [weak self] _ in + self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateOpaqueState: transition.updateOpaqueState, completion: { [weak self] _ in if let strongSelf = self { strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime) if !strongSelf.didSetReady { @@ -1993,7 +2340,7 @@ final class ChatMediaInputNode: ChatInputNode { private var isExpanded: Bool { var isExpanded: Bool = false - if let validLayout = self.validLayout, case let .media(_, maybeExpanded) = validLayout.8.inputMode, maybeExpanded != nil { + if let validLayout = self.validLayout, case let .media(_, maybeExpanded, _) = validLayout.8.inputMode, maybeExpanded != nil { isExpanded = true } return isExpanded @@ -2021,7 +2368,7 @@ final class ChatMediaInputNode: ChatInputNode { } var collectionListPanelOffset = self.currentCollectionListPanelOffset() - if self.panelExpanded { + if self.panelIsFocused { collectionListPanelOffset = 0.0 } @@ -2037,14 +2384,12 @@ final class ChatMediaInputNode: ChatInputNode { private func updatePaneClippingContainer(size: CGSize, offset: CGFloat, transition: ContainedViewLayoutTransition) { var offset = offset - var additionalOffset: CGFloat = 0.0 - if self.panelExpanded { + if self.panelIsFocused { offset = 0.0 - additionalOffset = 31.0 } - transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0 + additionalOffset), size: self.collectionListSeparator.bounds.size)) - transition.updateFrame(node: self.paneClippingContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0 + additionalOffset), size: size)) - transition.updateSublayerTransformOffset(layer: self.paneClippingContainer.layer, offset: CGPoint(x: 0.0, y: -offset - 41.0 - additionalOffset)) + transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0), size: self.collectionListSeparator.bounds.size)) + transition.updateFrame(node: self.paneClippingContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0), size: size)) + transition.updateSublayerTransformOffset(layer: self.paneClippingContainer.layer, offset: CGPoint(x: 0.0, y: -offset - 41.0)) } private func fixPaneScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState) { @@ -2059,7 +2404,7 @@ final class ChatMediaInputNode: ChatInputNode { } var collectionListPanelOffset = self.currentCollectionListPanelOffset() - if self.panelExpanded { + if self.panelIsFocused { collectionListPanelOffset = 0.0 } @@ -2073,6 +2418,15 @@ final class ChatMediaInputNode: ChatInputNode { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.panelIsFocused { + let convertedPoint = CGPoint(x: max(0.0, point.y), y: point.x) + if let result = self.listView.hitTest(convertedPoint, with: event) { + return result + } + if let result = self.gifListView.hitTest(convertedPoint, with: event) { + return result + } + } if let searchContainerNode = self.searchContainerNode { if let result = searchContainerNode.hitTest(point.offsetBy(dx: -searchContainerNode.frame.minX, dy: -searchContainerNode.frame.minY), with: event) { return result diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift b/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift index 13b73a9621..6b797d6d23 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift @@ -42,7 +42,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { case stickersMode(PresentationTheme, Bool) case savedGifs(PresentationTheme, Bool) case trendingGifs(PresentationTheme, Bool) - case gifEmotion(Int, PresentationTheme, String, Bool) + case gifEmotion(Int, PresentationTheme, String, TelegramMediaFile?, Bool) var stableId: ChatMediaInputPanelEntryStableId { switch self { @@ -66,7 +66,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { return .savedGifs case .trendingGifs: return .trendingGifs - case let .gifEmotion(_, _, emoji, _): + case let .gifEmotion(_, _, emoji, _, _): return .gifEmotion(emoji) } } @@ -133,8 +133,15 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { } else { return false } - case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji, lhsExpanded): - if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji, lhsExpanded == rhsExpanded { + case let .gifEmotion(lhsIndex, lhsTheme, lhsEmoji, lhsFile, lhsExpanded): + if case let .gifEmotion(rhsIndex, rhsTheme, rhsEmoji, rhsFile, rhsExpanded) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsEmoji == rhsEmoji, lhsExpanded == rhsExpanded { + if let lhsFile = lhsFile, let rhsFile = rhsFile { + if !lhsFile.isEqual(to: rhsFile) { + return false + } + } else if (lhsFile != nil) != (rhsFile != nil) { + return false + } return true } else { return false @@ -230,11 +237,11 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { default: return true } - case let .gifEmotion(lhsIndex, _, _, _): + case let .gifEmotion(lhsIndex, _, _, _, _): switch rhs { case .stickersMode, .savedGifs, .trendingGifs: return false - case let .gifEmotion(rhsIndex, _, _, _): + case let .gifEmotion(rhsIndex, _, _, _, _): return lhsIndex < rhsIndex default: return true @@ -256,12 +263,12 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { inputNodeInteraction.navigateToCollectionId(collectionId) }) case let .savedStickers(theme, expanded): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, expanded: expanded, selected: { + return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .savedStickers, theme: theme, expanded: expanded, selected: { let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue, id: 0) inputNodeInteraction.navigateToCollectionId(collectionId) }) case let .recentPacks(theme, expanded): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, expanded: expanded, selected: { + return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .recentStickers, theme: theme, expanded: expanded, selected: { let collectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0) inputNodeInteraction.navigateToCollectionId(collectionId) }) @@ -284,19 +291,19 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { inputNodeInteraction.navigateToCollectionId(info.id) }) case let .stickersMode(theme, expanded): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, expanded: expanded, selected: { + return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .stickersMode, theme: theme, expanded: expanded, selected: { inputNodeInteraction.navigateBackToStickers() }) case let .savedGifs(theme, expanded): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, expanded: expanded, selected: { + return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .savedGifs, theme: theme, expanded: expanded, selected: { inputNodeInteraction.setGifMode(.recent) }) case let .trendingGifs(theme, expanded): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, expanded: expanded, selected: { + return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .trendingGifs, theme: theme, expanded: expanded, selected: { inputNodeInteraction.setGifMode(.trending) }) - case let .gifEmotion(_, theme, emoji, expanded): - return ChatMediaInputMetaSectionItem(inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji), theme: theme, expanded: expanded, selected: { + case let .gifEmotion(_, theme, emoji, file, expanded): + return ChatMediaInputMetaSectionItem(account: context.account, inputNodeInteraction: inputNodeInteraction, type: .gifEmoji(emoji, file), theme: theme, expanded: expanded, selected: { inputNodeInteraction.setGifMode(.emojiSearch(emoji)) }) } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift index 37b192bbfa..0ccab940b2 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift @@ -133,16 +133,18 @@ final class ChatMediaInputPeerSpecificItemNode: ListViewItemNode { let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale) - expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0))) + expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0))) - expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0) let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height)) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize) let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.avatarNode.position.y - titleFrame.size.height), size: titleFrame.size) expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) + let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate + alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) + self.currentExpanded = expanded self.avatarNode.bounds = CGRect(origin: CGPoint(), size: imageSize) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift index 2cb3a142a2..9520e80399 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift @@ -124,16 +124,18 @@ final class ChatMediaInputRecentGifsItemNode: ListViewItemNode { let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale) - expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0))) + expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0))) - expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0) let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height)) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize) let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size) expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) + let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate + alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) + self.currentExpanded = expanded expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize)) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift index e9997e0b7f..0480a8c0bc 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift @@ -91,6 +91,7 @@ final class ChatMediaInputSettingsItemNode: ListViewItemNode { self.containerNode.addSubnode(self.scalingNode) self.scalingNode.addSubnode(self.buttonNode) + self.scalingNode.addSubnode(self.titleNode) self.scalingNode.addSubnode(self.imageNode) } @@ -114,18 +115,19 @@ final class ChatMediaInputSettingsItemNode: ListViewItemNode { let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale) - expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0))) + expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0))) - expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0) let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height)) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize) let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size) expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) + let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate + alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) + self.currentExpanded = expanded - } func updateAppearanceTransition(transition: ContainedViewLayoutTransition) { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift index 85f5ad45a9..1d6d49925d 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift @@ -87,6 +87,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { var inputNodeInteraction: ChatMediaInputNodeInteraction? var currentCollectionId: ItemCollectionId? + private var account: Account? private var currentThumbnailItem: StickerPackThumbnailItem? private var currentExpanded = false private var theme: PresentationTheme? @@ -172,7 +173,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme, expanded: Bool) { self.currentCollectionId = collectionId - + self.account = account var themeUpdated = false if self.theme !== theme { self.theme = theme @@ -185,7 +186,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { var resourceReference: MediaResourceReference? if let thumbnail = info.thumbnail { if info.flags.contains(.isAnimated) { - thumbnailItem = .animated(thumbnail.resource) + thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions) resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource) } else { thumbnailItem = .still(thumbnail) @@ -193,7 +194,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { } } else if let item = item { if item.file.isAnimatedSticker { - thumbnailItem = .animated(item.file.resource) + thumbnailItem = .animated(item.file.resource, item.file.dimensions ?? PixelDimensions(width: 100, height: 100)) resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource) } else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil)) @@ -210,33 +211,36 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { if self.currentThumbnailItem != thumbnailItem { self.currentThumbnailItem = thumbnailItem + let thumbnailDimensions = PixelDimensions(width: 512, height: 512) if let thumbnailItem = thumbnailItem { switch thumbnailItem { case let .still(representation): imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize) - let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) + let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 6.0), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets())) imageApply() self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true)) - case let .animated(resource): - let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) + case let .animated(resource, _): + let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets())) imageApply() 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) } else { self.scalingNode.addSubnode(animatedStickerNode) } - animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached) + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 128, height: 128, mode: .cached) } animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers } @@ -247,7 +251,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { if let placeholderNode = self.placeholderNode { let imageSize = boundingImageSize - placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, imageSize: CGSize(width: 100.0, height: 100.0)) + placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, imageSize: thumbnailDimensions.cgSize) } self.updateIsHighlighted() @@ -259,16 +263,18 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale) - expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0))) + expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0))) - expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0) - let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height)) + let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 4.0, height: expandedBoundingSize.height)) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize) let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size) expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) + let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate + alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) + self.currentExpanded = expanded self.imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize) @@ -311,4 +317,74 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { override func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } + + override func isReorderable(at point: CGPoint) -> Bool { + if self.bounds.contains(point) { + return true + } + return false + } + + override func snapshotForReordering() -> UIView? { + if let account = account, let thumbnailItem = self.currentThumbnailItem { + var imageSize = boundingImageSize + let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false + let containerNode = ASDisplayNode() + let scalingNode = ASDisplayNode() + containerNode.addSubnode(scalingNode) + containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + + var snapshotImageNode: TransformImageNode? + var snapshotAnimationNode: AnimatedStickerNode? + switch thumbnailItem { + case let .still(representation): + imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize) + + let imageNode = TransformImageNode() + let imageApply = imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 6.0), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets())) + imageApply() + imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true)) + scalingNode.addSubnode(imageNode) + + snapshotImageNode = imageNode + case let .animated(resource, _): + let animatedStickerNode = AnimatedStickerNode() + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 128, height: 128, mode: .direct(cachePathPrefix: nil)) + animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers + scalingNode.addSubnode(animatedStickerNode) + + animatedStickerNode.cloneCurrentFrame(from: self.animatedStickerNode) + animatedStickerNode.play(fromIndex: self.animatedStickerNode?.currentFrameIndex) + + snapshotAnimationNode = animatedStickerNode + } + + containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize) + scalingNode.bounds = CGRect(origin: CGPoint(), size: expandedBoundingSize) + + if let titleView = self.titleNode.view.snapshotContentTree() { + titleView.frame = self.titleNode.frame + scalingNode.view.addSubview(titleView) + } + + let imageFrame = CGRect(origin: CGPoint(x: (expandedBoundingSize.height - imageSize.width) / 2.0, y: (expandedBoundingSize.width - imageSize.height) / 2.0), size: imageSize) + if let imageNode = snapshotImageNode { + imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize) + imageNode.position = imageFrame.center + } + if let animatedStickerNode = snapshotAnimationNode { + animatedStickerNode.frame = imageFrame + animatedStickerNode.updateLayout(size: imageFrame.size) + } + + let expanded = self.currentExpanded + let scale = expanded ? 1.0 : boundingImageScale + let boundsSize = expanded ? expandedBoundingSize : CGSize(width: boundingSize.height, height: boundingSize.height) + scalingNode.transform = CATransform3DMakeScale(scale, scale, 1.0) + scalingNode.position = CGPoint(x: boundsSize.width / 2.0 + 3.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0) - 3.0) + + return containerNode.view + } + return nil + } } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift index e6d95c017a..fcc255d156 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift @@ -28,7 +28,10 @@ private func fixGridScrolling(_ gridNode: GridNode) { var scrollIndex: Int? if itemFrame.minY + itemFrame.height * 0.6 < contentInset { for i in 0 ..< gridNode.items.count { - if let _ = gridNode.items[i] as? ChatMediaInputStickerGridItem { + if let _ = gridNode.items[i] as? StickerPaneTrendingListGridItem { + scrollIndex = i + break + } else if let _ = gridNode.items[i] as? ChatMediaInputStickerGridItem { scrollIndex = i break } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift index 81de3c865e..f27923b5c8 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift @@ -133,7 +133,6 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode { if self.elevated != elevated { self.elevated = elevated - self.badgeBackground.isHidden = !self.elevated } self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedBoundingSize) @@ -143,14 +142,17 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode { let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale) - expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0))) + expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0))) - expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0) let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height)) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize) let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size) expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame) + expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001) + + let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate + alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0) self.currentExpanded = expanded diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index 841078d66d..f4cc8e02f3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -297,9 +297,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let incoming = message.effectivelyIncoming(context.account.peerId) - var horizontalInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0) + var horizontalInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0) if displayLine { - horizontalInsets.left += 10.0 + horizontalInsets.left += 12.0 } var preferMediaBeforeText = false @@ -612,9 +612,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { return (initialWidth, { constrainedSize, position in var insets = UIEdgeInsets(top: 0.0, left: horizontalInsets.left, bottom: 5.0, right: horizontalInsets.right) + var lineInsets = insets switch position { case .linear(.None, _): insets.top += 8.0 + lineInsets.top += 8.0 + 8.0 default: break } @@ -705,7 +707,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { boundingSize.width = max(boundingSize.width, videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight) } - lineHeight += insets.top + insets.bottom + lineHeight += lineInsets.top + lineInsets.bottom var imageApply: (() -> Void)? if let inlineImageSize = inlineImageSize, let inlineImageDimensions = inlineImageDimensions { @@ -731,7 +733,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } titleColor = presentationData.theme.theme.chat.message.incoming.accentTextColor let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty) - titleHighlightedColor = bubbleColor.fill + titleHighlightedColor = bubbleColor.fill[0] } else { buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(presentationData.theme.theme)! buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(presentationData.theme.theme)! @@ -741,7 +743,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } titleColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty) - titleHighlightedColor = bubbleColor.fill + titleHighlightedColor = bubbleColor.fill[0] } let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, buttonIconImage, buttonHighlightedIconImage, actionTitle, titleColor, titleHighlightedColor) boundingSize.width = max(buttonWidth, boundingSize.width) @@ -806,7 +808,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { var actionButtonSizeAndApply: ((CGSize, () -> ChatMessageAttachedContentButtonNode))? if let continueActionButtonLayout = continueActionButtonLayout { - let (size, apply) = continueActionButtonLayout(boundingWidth - 13.0 - insets.right) + let (size, apply) = continueActionButtonLayout(boundingWidth - 12.0 - insets.right) actionButtonSizeAndApply = (size, apply) adjustedBoundingSize.width = max(adjustedBoundingSize.width, insets.left + size.width + insets.right) adjustedBoundingSize.height += 7.0 + size.height @@ -976,7 +978,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } } - buttonNode.frame = CGRect(origin: CGPoint(x: 13.0, y: adjustedLineHeight - insets.top - insets.bottom - 2.0 + 6.0), size: size) + buttonNode.frame = CGRect(origin: CGPoint(x: 12.0, y: adjustedLineHeight - insets.top - insets.bottom - 2.0 + 6.0), size: size) } else if let buttonNode = strongSelf.buttonNode { buttonNode.removeFromSupernode() strongSelf.buttonNode = nil 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/ChatMessageBubbleContentCalclulateImageCorners.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift index 7d3fbc07f6..a99c5992df 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift @@ -77,7 +77,7 @@ func chatMessageBubbleImageContentCorners(relativeContentPosition position: Chat case .None: colors = chatPresentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper } - if colors.fill == colors.stroke || colors.stroke.alpha.isZero { + if colors.fill[0] == colors.stroke || colors.stroke.alpha.isZero { bubbleInsets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) } else { bubbleInsets = layoutConstants.bubble.strokeInsets @@ -110,7 +110,7 @@ func chatMessageBubbleImageContentCorners(relativeContentPosition position: Chat if case .color = chatPresentationData.theme.wallpaper { let colors: PresentationThemeBubbleColorComponents colors = chatPresentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper - if colors.fill == colors.stroke || colors.stroke.alpha.isZero { + if colors.fill[0] == colors.stroke || colors.stroke.alpha.isZero { bubbleInsets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) } else { bubbleInsets = layoutConstants.bubble.strokeInsets @@ -135,7 +135,7 @@ func chatMessageBubbleImageContentCorners(relativeContentPosition position: Chat if case .color = chatPresentationData.theme.wallpaper { let colors: PresentationThemeBubbleColorComponents colors = chatPresentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper - if colors.fill == colors.stroke || colors.stroke.alpha.isZero { + if colors.fill[0] == colors.stroke || colors.stroke.alpha.isZero { bubbleInsets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) } else { bubbleInsets = layoutConstants.bubble.strokeInsets diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 96a32beaf8..43b8253dc0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -147,6 +147,12 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ break inner } } + + if message.adAttribute != nil { + result.removeAll() + + result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default))) + } if isUnsupportedMedia { result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default))) @@ -177,6 +183,10 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == firstMessage.id { hasDiscussion = false } + + if firstMessage.adAttribute != nil { + hasDiscussion = false + } if hasDiscussion { var canComment = false @@ -371,7 +381,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode selectionBackgroundFrame = selectionBackgroundFrame.inset(by: selectionInsets) let bubbleColor = graphics.hasWallpaper ? messageTheme.bubble.withWallpaper.fill : messageTheme.bubble.withoutWallpaper.fill - let selectionColor = bubbleColor.withAlphaComponent(1.0).mixedWith(messageTheme.accentTextColor.withAlphaComponent(1.0), alpha: 0.08) + let selectionColor = bubbleColor[0].withAlphaComponent(1.0).mixedWith(messageTheme.accentTextColor.withAlphaComponent(1.0), alpha: 0.08) self.selectionBackgroundNode?.backgroundColor = selectionColor self.selectionBackgroundNode?.frame = selectionBackgroundFrame @@ -1171,6 +1181,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode ignoreForward = true effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) displayAuthorInfo = !mergedTop.merged && incoming + } else if let _ = item.content.firstMessage.adAttribute, let author = item.content.firstMessage.author { + ignoreForward = true + effectiveAuthor = author + displayAuthorInfo = !mergedTop.merged && incoming } else { effectiveAuthor = firstMessage.author @@ -1287,6 +1301,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if isPreview { needShareButton = false } + if item.content.firstMessage.adAttribute != nil { + needShareButton = false + } var tmpWidth: CGFloat if allowFullWidth { @@ -1549,7 +1566,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } if initialDisplayHeader && displayAuthorInfo { - if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info, item.content.firstMessage.adAttribute == nil { authorNameString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) authorNameColor = chatMessagePeerIdColors[Int(clamping: peer.id.id._internalGetInt64Value() % 7)] } else if let effectiveAuthor = effectiveAuthor { diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index f9cc25ac81..fb63b3b837 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -224,7 +224,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { titleColor = item.presentationData.theme.theme.chat.message.incoming.accentTextColor let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: true, wallpaper: !item.presentationData.theme.wallpaper.isEmpty) - titleHighlightedColor = bubbleColors.fill + titleHighlightedColor = bubbleColors.fill[0] avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor } else { buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(item.presentationData.theme.theme)! @@ -232,7 +232,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { titleColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: false, wallpaper: !item.presentationData.theme.wallpaper.isEmpty) - titleHighlightedColor = bubbleColors.fill + titleHighlightedColor = bubbleColors.fill[0] avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor } diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index 20ea32ca77..79f72e066f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -16,6 +16,9 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou private let selectAll: Bool var shouldBeDismissed: Signal { + if self.message.adAttribute != nil { + return .single(false) + } let viewKey = PostboxViewKey.messages(Set([self.message.id])) return self.postbox.combinedView(keys: [viewKey]) |> map { views -> Bool in diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index 7e901a3377..e5d284ab45 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -131,7 +131,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } else { colors = item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper } - if colors.fill == colors.stroke || colors.stroke.alpha.isZero { + if colors.fill[0] == colors.stroke || colors.stroke.alpha.isZero { bubbleInsets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) } else { bubbleInsets = layoutConstants.bubble.strokeInsets diff --git a/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift index db5623a2eb..db3b94620b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift @@ -48,13 +48,13 @@ final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNod buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(presentationData.theme.theme)! titleColor = presentationData.theme.theme.chat.message.incoming.accentTextColor let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty) - titleHighlightedColor = bubbleColor.fill + titleHighlightedColor = bubbleColor.fill[0] } else { buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(presentationData.theme.theme)! buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(presentationData.theme.theme)! titleColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty) - titleHighlightedColor = bubbleColor.fill + titleHighlightedColor = bubbleColor.fill[0] } let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, presentationData.strings.Conversation_UpdateTelegram, titleColor, titleHighlightedColor) diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index 076dc8dea3..66d6cea8e0 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -63,17 +63,21 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } self.contentNode.activateAction = { [weak self] in if let strongSelf = self, let item = strongSelf.item { - var webPageContent: TelegramMediaWebpageLoadedContent? - for media in item.message.media { - if let media = media as? TelegramMediaWebpage { - if case let .Loaded(content) = media.content { - webPageContent = content + if let _ = item.message.adAttribute, let author = item.message.author { + item.controllerInteraction.openPeer(author.id, .chat(textInputState: nil, subject: nil, peekData: nil), nil) + } else { + var webPageContent: TelegramMediaWebpageLoadedContent? + for media in item.message.media { + if let media = media as? TelegramMediaWebpage { + if case let .Loaded(content) = media.content { + webPageContent = content + } + break } - break } - } - if let webpage = webPageContent { - item.controllerInteraction.openUrl(webpage.url, false, nil, nil) + if let webpage = webPageContent { + item.controllerInteraction.openUrl(webpage.url, false, nil, nil) + } } } } @@ -108,6 +112,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { var actionIcon: ChatMessageAttachedContentActionIcon? var actionTitle: String? + + var displayLine: Bool = true if let webpage = webPageContent { let type = websiteType(of: webpage.websiteName) @@ -297,9 +303,33 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { break } } + } else if let _ = item.message.adAttribute { + title = nil + subtitle = nil + text = item.message.text + for attribute in item.message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + entities = attribute.entities + } + } + for media in item.message.media { + switch media { + case _ as TelegramMediaImage, _ as TelegramMediaFile: + mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) + default: + break + } + } + + if let author = item.message.author as? TelegramChannel, case .group = author.info { + actionTitle = item.presentationData.strings.Conversation_ViewGroup + } else { + actionTitle = item.presentationData.strings.Conversation_ViewChannel + } + displayLine = false } - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, true, layoutConstants, preparePosition, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) @@ -345,9 +375,22 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + guard let item = self.item else { + return .none + } if self.bounds.contains(point) { let contentNodeFrame = self.contentNode.frame let result = self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture, isEstimating: isEstimating) + + if item.message.adAttribute != nil { + if case .none = result { + if self.contentNode.hasActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY)) { + return .ignore + } + } + return result + } + switch result { case .none: break diff --git a/submodules/TelegramUI/Sources/ChatOverscrollControl.swift b/submodules/TelegramUI/Sources/ChatOverscrollControl.swift index 99ba5e4da8..fe2646605e 100644 --- a/submodules/TelegramUI/Sources/ChatOverscrollControl.swift +++ b/submodules/TelegramUI/Sources/ChatOverscrollControl.swift @@ -1,10 +1,14 @@ import UIKit import ComponentFlow import Display +import AsyncDisplayKit import TelegramCore import Postbox import AccountContext import AvatarNode +import TextFormat +import Markdown +import WallpaperBackgroundNode final class BlurredRoundedRectangle: Component { let color: UIColor @@ -196,6 +200,9 @@ final class CheckComponent: Component { } final class View: UIView { + private var currentValue: CGFloat? + private var animator: DisplayLinkAnimator? + init() { super.init(frame: CGRect()) } @@ -204,10 +211,8 @@ final class CheckComponent: Component { preconditionFailure() } - func update(component: CheckComponent, availableSize: CGSize, transition: Transition) -> CGSize { + private func updateContent(size: CGSize, color: UIColor, lineWidth: CGFloat, value: CGFloat) { func draw(context: CGContext) { - let size = availableSize - let diameter = size.width let factor = diameter / 50.0 @@ -215,19 +220,17 @@ final class CheckComponent: Component { context.saveGState() context.setBlendMode(.normal) - context.setFillColor(component.color.cgColor) - context.setStrokeColor(component.color.cgColor) + context.setFillColor(color.cgColor) + context.setStrokeColor(color.cgColor) let center = CGPoint(x: diameter / 2.0, y: diameter / 2.0) - let lineWidth = component.lineWidth - context.setLineWidth(max(1.7, lineWidth * factor)) context.setLineCap(.round) context.setLineJoin(.round) context.setMiterLimit(10.0) - let progress = component.value + let progress = value let firstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0)) var s = CGPoint(x: center.x - 10.0 * factor, y: center.y + 1.0 * factor) @@ -257,7 +260,7 @@ final class CheckComponent: Component { } if #available(iOS 10.0, *) { - let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: availableSize)) + let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: size)) let image = renderer.image { context in UIGraphicsPushContext(context.cgContext) draw(context: context.cgContext) @@ -265,11 +268,37 @@ final class CheckComponent: Component { } self.layer.contents = image.cgImage } else { - UIGraphicsBeginImageContextWithOptions(availableSize, false, 0.0) + UIGraphicsBeginImageContextWithOptions(size, false, 0.0) draw(context: UIGraphicsGetCurrentContext()!) self.layer.contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage UIGraphicsEndImageContext() } + } + + func update(component: CheckComponent, availableSize: CGSize, transition: Transition) -> CGSize { + if let currentValue = self.currentValue, currentValue != component.value, case .curve = transition.animation { + self.animator?.invalidate() + + let animator = DisplayLinkAnimator(duration: 0.15, from: currentValue, to: component.value, update: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.updateContent(size: availableSize, color: component.color, lineWidth: component.lineWidth, value: value) + }, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.animator?.invalidate() + strongSelf.animator = nil + }) + self.animator = animator + } else { + if self.animator == nil { + self.updateContent(size: availableSize, color: component.color, lineWidth: component.lineWidth, value: component.value) + } + } + + self.currentValue = component.value return availableSize } @@ -284,13 +313,143 @@ final class CheckComponent: Component { } } +final class BadgeComponent: CombinedComponent { + let count: Int + let backgroundColor: UIColor + let foregroundColor: UIColor + let rect: CGRect + let withinSize: CGSize + let wallpaperNode: WallpaperBackgroundNode? + + init( + count: Int, + backgroundColor: UIColor, + foregroundColor: UIColor, + rect: CGRect, + withinSize: CGSize, + wallpaperNode: WallpaperBackgroundNode? + ) { + self.count = count + self.backgroundColor = backgroundColor + self.foregroundColor = foregroundColor + self.rect = rect + self.withinSize = withinSize + self.wallpaperNode = wallpaperNode + } + + static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool { + if lhs.count != rhs.count { + return false + } + if !lhs.backgroundColor.isEqual(rhs.backgroundColor) { + return false + } + if !lhs.foregroundColor.isEqual(rhs.foregroundColor) { + return false + } + if lhs.rect != rhs.rect { + return false + } + if lhs.withinSize != rhs.withinSize { + return false + } + if lhs.wallpaperNode != rhs.wallpaperNode { + return false + } + return true + } + + static var body: Body { + let background = Child(WallpaperBlurComponent.self) + let text = Child(Text.self) + + return { context in + let text = text.update( + component: Text( + text: "\(context.component.count)", + font: Font.regular(13.0), + color: context.component.foregroundColor + ), + availableSize: CGSize(width: 100.0, height: 100.0), + transition: .immediate + ) + + let height = text.size.height + 4.0 + let backgroundSize = CGSize(width: max(height, text.size.width + 8.0), height: height) + + let background = background.update( + component: WallpaperBlurComponent( + rect: CGRect(origin: context.component.rect.origin, size: backgroundSize), + withinSize: context.component.withinSize, + color: context.component.backgroundColor, + wallpaperNode: context.component.wallpaperNode + ), + availableSize: backgroundSize, + transition: .immediate + ) + + context.add(background + .position(CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0)) + .cornerRadius(min(backgroundSize.width, backgroundSize.height) / 2.0) + .clipsToBounds(true) + ) + + context.add(text + .position(CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0)) + ) + + return backgroundSize + } + } +} + final class AvatarComponent: Component { + final class Badge: Equatable { + let count: Int + let backgroundColor: UIColor + let foregroundColor: UIColor + + init(count: Int, backgroundColor: UIColor, foregroundColor: UIColor) { + self.count = count + self.backgroundColor = backgroundColor + self.foregroundColor = foregroundColor + } + + static func ==(lhs: Badge, rhs: Badge) -> Bool { + if lhs.count != rhs.count { + return false + } + if !lhs.backgroundColor.isEqual(rhs.backgroundColor) { + return false + } + if !lhs.foregroundColor.isEqual(rhs.foregroundColor) { + return false + } + return true + } + } + let context: AccountContext let peer: EnginePeer + let badge: Badge? + let rect: CGRect + let withinSize: CGSize + let wallpaperNode: WallpaperBackgroundNode? - init(context: AccountContext, peer: EnginePeer) { + init( + context: AccountContext, + peer: EnginePeer, + badge: Badge?, + rect: CGRect, + withinSize: CGSize, + wallpaperNode: WallpaperBackgroundNode? + ) { self.context = context self.peer = peer + self.badge = badge + self.rect = rect + self.withinSize = withinSize + self.wallpaperNode = wallpaperNode } static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool { @@ -300,14 +459,29 @@ final class AvatarComponent: Component { if lhs.peer != rhs.peer { return false } + if lhs.badge != rhs.badge { + return false + } + if lhs.rect != rhs.rect { + return false + } + if lhs.withinSize != rhs.withinSize { + return false + } + if lhs.wallpaperNode !== rhs.wallpaperNode { + return false + } return true } final class View: UIView { private let avatarNode: AvatarNode + private let avatarMask: CAShapeLayer + private var badgeView: ComponentHostView? init() { self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) + self.avatarMask = CAShapeLayer() super.init(frame: CGRect()) @@ -322,6 +496,59 @@ final class AvatarComponent: Component { self.avatarNode.frame = CGRect(origin: CGPoint(), size: availableSize) self.avatarNode.setPeer(context: component.context, theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme, peer: component.peer, synchronousLoad: true) + if let badge = component.badge { + let badgeView: ComponentHostView + let animateIn = self.badgeView == nil + if let current = self.badgeView { + badgeView = current + } else { + badgeView = ComponentHostView() + self.badgeView = badgeView + self.addSubview(badgeView) + } + + let badgeSize = badgeView.update( + transition: .immediate, + component: AnyComponent(BadgeComponent( + count: badge.count, + backgroundColor: badge.backgroundColor, + foregroundColor: badge.foregroundColor, + rect: CGRect(origin: component.rect.offsetBy(dx: 0.0, dy: 0.0).origin, size: CGSize()), + withinSize: component.withinSize, + wallpaperNode: component.wallpaperNode + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0 + )) + let badgeDiameter = min(badgeSize.width, badgeSize.height) + let circlePoint = CGPoint( + x: availableSize.width / 2.0 + cos(CGFloat.pi / 4) * availableSize.width / 2.0, + y: availableSize.height / 2.0 - sin(CGFloat.pi / 4) * availableSize.width / 2.0 + ) + badgeView.frame = CGRect(origin: CGPoint(x: circlePoint.x - badgeDiameter / 2.0, y: circlePoint.y - badgeDiameter / 2.0), size: badgeSize) + + self.avatarMask.frame = self.avatarNode.bounds + self.avatarMask.fillRule = .evenOdd + + let path = UIBezierPath(rect: self.avatarMask.bounds) + path.append(UIBezierPath(roundedRect: badgeView.frame.insetBy(dx: -2.0, dy: -2.0), cornerRadius: badgeDiameter / 2.0)) + self.avatarMask.path = path.cgPath + + self.avatarNode.view.layer.mask = self.avatarMask + + if animateIn { + badgeView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.14) + } + } else if let badgeView = self.badgeView { + self.badgeView = nil + + badgeView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.14, removeOnCompletion: false, completion: { [weak badgeView] _ in + badgeView?.removeFromSuperview() + }) + + self.avatarNode.view.layer.mask = nil + } + return availableSize } } @@ -335,32 +562,142 @@ final class AvatarComponent: Component { } } -final class ChatOverscrollControl: CombinedComponent { - let text: String +private final class WallpaperBlurNode: ASDisplayNode { + private var backgroundNode: WallpaperBackgroundNode.BubbleBackgroundNode? + private let colorNode: ASDisplayNode + + override init() { + self.colorNode = ASDisplayNode() + + super.init() + + self.addSubnode(self.colorNode) + } + + func update(rect: CGRect, within size: CGSize, color: UIColor, wallpaperNode: WallpaperBackgroundNode?, transition: ContainedViewLayoutTransition) { + var transition = transition + if self.backgroundNode == nil { + if let backgroundNode = wallpaperNode?.makeBubbleBackground(for: .free) { + self.backgroundNode = backgroundNode + self.insertSubnode(backgroundNode, at: 0) + transition = .immediate + } + } + + self.colorNode.backgroundColor = color + transition.updateFrame(node: self.colorNode, frame: CGRect(origin: CGPoint(), size: rect.size)) + + if let backgroundNode = self.backgroundNode { + transition.updateFrame(node: backgroundNode, frame: CGRect(origin: CGPoint(), size: rect.size)) + backgroundNode.update(rect: rect, within: size, transition: transition) + } + } +} + +private final class WallpaperBlurComponent: Component { + let rect: CGRect + let withinSize: CGSize + let color: UIColor + let wallpaperNode: WallpaperBackgroundNode? + + init( + rect: CGRect, + withinSize: CGSize, + color: UIColor, + wallpaperNode: WallpaperBackgroundNode? + ) { + self.rect = rect + self.withinSize = withinSize + self.color = color + self.wallpaperNode = wallpaperNode + } + + static func ==(lhs: WallpaperBlurComponent, rhs: WallpaperBlurComponent) -> Bool { + if lhs.rect != rhs.rect { + return false + } + if lhs.withinSize != rhs.withinSize { + return false + } + if !lhs.color.isEqual(rhs.color) { + return false + } + if lhs.wallpaperNode !== rhs.wallpaperNode { + return false + } + return true + } + + final class View: UIView { + private let background: WallpaperBlurNode + + init() { + self.background = WallpaperBlurNode() + + super.init(frame: CGRect()) + + self.addSubview(self.background.view) + } + + required init?(coder aDecoder: NSCoder) { + preconditionFailure() + } + + func update(component: WallpaperBlurComponent, availableSize: CGSize, transition: Transition) -> CGSize { + transition.setFrame(view: self.background.view, frame: CGRect(origin: CGPoint(), size: availableSize)) + self.background.update(rect: component.rect, within: component.withinSize, color: component.color, wallpaperNode: component.wallpaperNode, transition: .immediate) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + +final class OverscrollContentsComponent: Component { + let context: AccountContext let backgroundColor: UIColor let foregroundColor: UIColor let peer: EnginePeer? - let context: AccountContext - let expandDistance: CGFloat + let unreadCount: Int + let location: TelegramEngine.NextUnreadChannelLocation + let expandOffset: CGFloat + let absoluteRect: CGRect + let absoluteSize: CGSize + let wallpaperNode: WallpaperBackgroundNode? init( - text: String, + context: AccountContext, backgroundColor: UIColor, foregroundColor: UIColor, peer: EnginePeer?, - context: AccountContext, - expandDistance: CGFloat + unreadCount: Int, + location: TelegramEngine.NextUnreadChannelLocation, + expandOffset: CGFloat, + absoluteRect: CGRect, + absoluteSize: CGSize, + wallpaperNode: WallpaperBackgroundNode? ) { - self.text = text + self.context = context self.backgroundColor = backgroundColor self.foregroundColor = foregroundColor self.peer = peer - self.context = context - self.expandDistance = expandDistance + self.unreadCount = unreadCount + self.location = location + self.expandOffset = expandOffset + self.absoluteRect = absoluteRect + self.absoluteSize = absoluteSize + self.wallpaperNode = wallpaperNode } - static func ==(lhs: ChatOverscrollControl, rhs: ChatOverscrollControl) -> Bool { - if lhs.text != rhs.text { + static func ==(lhs: OverscrollContentsComponent, rhs: OverscrollContentsComponent) -> Bool { + if lhs.context !== rhs.context { return false } if !lhs.backgroundColor.isEqual(rhs.backgroundColor) { @@ -372,166 +709,391 @@ final class ChatOverscrollControl: CombinedComponent { if lhs.peer != rhs.peer { return false } + if lhs.unreadCount != rhs.unreadCount { + return false + } + if lhs.location != rhs.location { + return false + } + if lhs.expandOffset != rhs.expandOffset { + return false + } + if lhs.absoluteRect != rhs.absoluteRect { + return false + } + if lhs.absoluteSize != rhs.absoluteSize { + return false + } + if lhs.wallpaperNode !== rhs.wallpaperNode { + return false + } + return true + } + + final class View: UIView { + private let backgroundScalingContainer: ASDisplayNode + private let backgroundNode: WallpaperBlurNode + private let backgroundFolderMask: UIImageView + private let backgroundClippingNode: ASDisplayNode + private let avatarView = ComponentHostView() + private let checkView = ComponentHostView() + private let arrowNode: ASImageNode + private let avatarScalingContainer: ASDisplayNode + private let avatarExtraScalingContainer: ASDisplayNode + private let avatarOffsetContainer: ASDisplayNode + private let arrowOffsetContainer: ASDisplayNode + + private let titleOffsetContainer: ASDisplayNode + private let titleBackgroundNode: WallpaperBlurNode + private let titleNode: ImmediateTextNode + + private var isFullyExpanded: Bool = false + + private var validForegroundColor: UIColor? + + init() { + self.backgroundScalingContainer = ASDisplayNode() + self.backgroundNode = WallpaperBlurNode() + self.backgroundNode.clipsToBounds = true + + self.backgroundFolderMask = UIImageView() + self.backgroundFolderMask.image = UIImage(bundleImageName: "Chat/OverscrollFolder")?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 40) + + self.backgroundClippingNode = ASDisplayNode() + self.backgroundClippingNode.clipsToBounds = true + self.arrowNode = ASImageNode() + self.avatarScalingContainer = ASDisplayNode() + self.avatarExtraScalingContainer = ASDisplayNode() + self.avatarOffsetContainer = ASDisplayNode() + self.arrowOffsetContainer = ASDisplayNode() + + self.titleOffsetContainer = ASDisplayNode() + self.titleBackgroundNode = WallpaperBlurNode() + self.titleBackgroundNode.clipsToBounds = true + self.titleNode = ImmediateTextNode() + + super.init(frame: CGRect()) + + self.addSubview(self.backgroundScalingContainer.view) + + self.backgroundClippingNode.addSubnode(self.backgroundNode) + self.backgroundScalingContainer.addSubnode(self.backgroundClippingNode) + + self.avatarScalingContainer.view.addSubview(self.avatarView) + self.avatarScalingContainer.view.addSubview(self.checkView) + self.avatarExtraScalingContainer.addSubnode(self.avatarScalingContainer) + self.avatarOffsetContainer.addSubnode(self.avatarExtraScalingContainer) + self.arrowOffsetContainer.addSubnode(self.arrowNode) + self.backgroundNode.addSubnode(self.arrowOffsetContainer) + self.addSubnode(self.avatarOffsetContainer) + + self.titleOffsetContainer.addSubnode(self.titleBackgroundNode) + self.titleOffsetContainer.addSubnode(self.titleNode) + self.addSubnode(self.titleOffsetContainer) + } + + required init?(coder aDecoder: NSCoder) { + preconditionFailure() + } + + func update(component: OverscrollContentsComponent, availableSize: CGSize, transition: Transition) -> CGSize { + if let _ = component.peer { + self.avatarView.isHidden = false + self.checkView.isHidden = true + } else { + self.avatarView.isHidden = true + self.checkView.isHidden = false + } + + let fullHeight: CGFloat = 94.0 + let backgroundWidth: CGFloat = 56.0 + let minBackgroundHeight: CGFloat = backgroundWidth + 5.0 + let avatarInset: CGFloat = 6.0 + + let isFullyExpanded = component.expandOffset >= fullHeight + + let isFolderMask: Bool + switch component.location { + case .archived, .folder: + isFolderMask = true + default: + isFolderMask = false + } + + let expandProgress: CGFloat = max(0.1, min(1.0, component.expandOffset / fullHeight)) + + func interpolate(from: CGFloat, to: CGFloat, value: CGFloat) -> CGFloat { + return (1.0 - value) * from + value * to + } + + let backgroundHeight: CGFloat = interpolate(from: minBackgroundHeight, to: fullHeight, value: expandProgress) + + let backgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - backgroundWidth) / 2.0), y: fullHeight - backgroundHeight), size: CGSize(width: backgroundWidth, height: backgroundHeight)) + + let alphaProgress: CGFloat = max(0.0, min(1.0, component.expandOffset / 10.0)) + + let maxAvatarScale: CGFloat = 1.0 + var avatarExpandProgress: CGFloat = max(0.01, min(maxAvatarScale, component.expandOffset / fullHeight)) + avatarExpandProgress *= expandProgress + + let avatarOffsetProgress = interpolate(from: 0.1, to: 1.0, value: avatarExpandProgress) + + transition.setAlpha(view: self.backgroundScalingContainer.view, alpha: alphaProgress) + transition.setFrame(view: self.backgroundScalingContainer.view, frame: CGRect(origin: CGPoint(x: floor(availableSize.width / 2.0), y: fullHeight), size: CGSize(width: 0.0, height: 0.0))) + transition.setSublayerTransform(view: self.backgroundScalingContainer.view, transform: CATransform3DMakeScale(expandProgress, expandProgress, 1.0)) + + transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: fullHeight - backgroundFrame.size.height), size: backgroundFrame.size)) + self.backgroundNode.update(rect: backgroundFrame.offsetBy(dx: component.absoluteRect.minX, dy: component.absoluteRect.minY), within: component.absoluteSize, color: component.backgroundColor, wallpaperNode: component.wallpaperNode, transition: .immediate) + self.backgroundFolderMask.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) + + let avatarFrame = CGRect(origin: CGPoint(x: floor(-backgroundWidth / 2.0), y: floor(-backgroundWidth / 2.0)), size: CGSize(width: backgroundWidth, height: backgroundWidth)) + self.avatarView.frame = avatarFrame + + transition.setFrame(view: self.avatarOffsetContainer.view, frame: CGRect()) + transition.setFrame(view: self.avatarScalingContainer.view, frame: CGRect()) + transition.setFrame(view: self.avatarExtraScalingContainer.view, frame: CGRect(origin: CGPoint(x: availableSize.width / 2.0, y: fullHeight - backgroundWidth / 2.0), size: CGSize()).offsetBy(dx: 0.0, dy: (1.0 - avatarOffsetProgress) * backgroundWidth * 0.5)) + transition.setSublayerTransform(view: self.avatarScalingContainer.view, transform: CATransform3DMakeScale(avatarExpandProgress, avatarExpandProgress, 1.0)) + + let titleText: String + if let peer = component.peer { + titleText = peer.compactDisplayTitle + } else { + //TODO:localize + titleText = "You have no unread channels" + } + self.titleNode.attributedText = NSAttributedString(string: titleText, font: Font.semibold(13.0), textColor: component.foregroundColor) + let titleSize = self.titleNode.updateLayout(CGSize(width: availableSize.width - 32.0, height: 100.0)) + let titleBackgroundSize = CGSize(width: titleSize.width + 18.0, height: titleSize.height + 8.0) + let titleBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleBackgroundSize.width) / 2.0), y: fullHeight - titleBackgroundSize.height - 8.0), size: titleBackgroundSize) + self.titleBackgroundNode.frame = titleBackgroundFrame + self.titleBackgroundNode.update(rect: titleBackgroundFrame.offsetBy(dx: component.absoluteRect.minX, dy: component.absoluteRect.minY), within: component.absoluteSize, color: component.backgroundColor, wallpaperNode: component.wallpaperNode, transition: .immediate) + self.titleBackgroundNode.cornerRadius = min(titleBackgroundFrame.width, titleBackgroundFrame.height) / 2.0 + self.titleNode.frame = titleSize.centered(in: titleBackgroundFrame) + + let backgroundClippingFrame = CGRect(origin: CGPoint(x: floor(-backgroundWidth / 2.0), y: -fullHeight), size: CGSize(width: backgroundWidth, height: isFullyExpanded ? backgroundWidth : fullHeight)) + self.backgroundClippingNode.cornerRadius = isFolderMask ? 10.0 : backgroundWidth / 2.0 + self.backgroundNode.cornerRadius = isFolderMask ? 0.0 : backgroundWidth / 2.0 + self.backgroundNode.view.mask = isFolderMask ? self.backgroundFolderMask : nil + + if !(self.validForegroundColor?.isEqual(component.foregroundColor) ?? false) { + self.validForegroundColor = component.foregroundColor + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/OverscrollArrow"), color: component.foregroundColor) + } + + if let arrowImage = self.arrowNode.image { + self.arrowNode.frame = CGRect(origin: CGPoint(x: floor((backgroundWidth - arrowImage.size.width) / 2.0), y: floor((backgroundWidth - arrowImage.size.width) / 2.0)), size: arrowImage.size) + } + + let transformTransition: ContainedViewLayoutTransition + if self.isFullyExpanded != isFullyExpanded { + self.isFullyExpanded = isFullyExpanded + transformTransition = .animated(duration: 0.12, curve: .easeInOut) + + if isFullyExpanded { + func animateBounce(layer: CALayer) { + layer.animateScale(from: 1.0, to: 1.1, duration: 0.1, removeOnCompletion: false, completion: { [weak layer] _ in + layer?.animateScale(from: 1.1, to: 1.0, duration: 0.14, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + }) + } + + animateBounce(layer: self.backgroundClippingNode.layer) + animateBounce(layer: self.avatarExtraScalingContainer.layer) + + func animateOffsetBounce(layer: CALayer) { + let firstAnimation = layer.makeAnimation(from: 0.0 as NSNumber, to: -5.0 as NSNumber, keyPath: "transform.translation.y", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.1, removeOnCompletion: false, additive: true, completion: { [weak layer] _ in + guard let layer = layer else { + return + } + let secondAnimation = layer.makeAnimation(from: -5.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.translation.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.14, removeOnCompletion: true, additive: true) + layer.add(secondAnimation, forKey: "bounceY") + }) + layer.add(firstAnimation, forKey: "bounceY") + } + + animateOffsetBounce(layer: self.layer) + } + } else { + transformTransition = .immediate + } + + let checkSize: CGFloat = 56.0 + self.checkView.frame = CGRect(origin: CGPoint(x: floor(-checkSize / 2.0), y: floor(-checkSize / 2.0)), size: CGSize(width: checkSize, height: checkSize)) + let _ = self.checkView.update( + transition: Transition(animation: transformTransition.isAnimated ? .curve(duration: 0.2, curve: .easeInOut) : .none), + component: AnyComponent(CheckComponent( + color: component.foregroundColor, + lineWidth: 3.0, + value: isFullyExpanded ? 1.0 : 0.0 + )), + environment: {}, + containerSize: CGSize(width: checkSize, height: checkSize) + ) + + if let peer = component.peer { + let _ = self.avatarView.update( + transition: Transition(animation: transformTransition.isAnimated ? .curve(duration: 0.2, curve: .easeInOut) : .none), + component: AnyComponent(AvatarComponent( + context: component.context, + peer: peer, + badge: isFullyExpanded ? AvatarComponent.Badge(count: component.unreadCount, backgroundColor: component.backgroundColor, foregroundColor: component.foregroundColor) : nil, + rect: avatarFrame.offsetBy(dx: self.avatarExtraScalingContainer.frame.midX + component.absoluteRect.minX, dy: self.avatarExtraScalingContainer.frame.midY + component.absoluteRect.minY), + withinSize: component.absoluteSize, + wallpaperNode: component.wallpaperNode + )), + environment: {}, + containerSize: self.avatarView.bounds.size + ) + } + + transformTransition.updateAlpha(node: self.backgroundNode, alpha: (isFullyExpanded && component.peer != nil) ? 0.0 : 1.0) + transformTransition.updateAlpha(node: self.arrowNode, alpha: isFullyExpanded ? 0.0 : 1.0) + + transformTransition.updateSublayerTransformOffset(layer: self.avatarOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0)) + transformTransition.updateSublayerTransformOffset(layer: self.arrowOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0)) + + transformTransition.updateSublayerTransformOffset(layer: self.titleOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? 0.0 : (titleBackgroundSize.height + 50.0))) + + transformTransition.updateSublayerTransformScale(node: self.avatarExtraScalingContainer, scale: isFullyExpanded ? 1.0 : ((backgroundWidth - avatarInset * 2.0) / backgroundWidth)) + + transformTransition.updateFrame(node: self.backgroundClippingNode, frame: backgroundClippingFrame) + + return CGSize(width: availableSize.width, height: fullHeight) + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + +final class ChatOverscrollControl: CombinedComponent { + let backgroundColor: UIColor + let foregroundColor: UIColor + let peer: EnginePeer? + let unreadCount: Int + let location: TelegramEngine.NextUnreadChannelLocation + let context: AccountContext + let expandDistance: CGFloat + let absoluteRect: CGRect + let absoluteSize: CGSize + let wallpaperNode: WallpaperBackgroundNode? + + init( + backgroundColor: UIColor, + foregroundColor: UIColor, + peer: EnginePeer?, + unreadCount: Int, + location: TelegramEngine.NextUnreadChannelLocation, + context: AccountContext, + expandDistance: CGFloat, + absoluteRect: CGRect, + absoluteSize: CGSize, + wallpaperNode: WallpaperBackgroundNode? + ) { + self.backgroundColor = backgroundColor + self.foregroundColor = foregroundColor + self.peer = peer + self.unreadCount = unreadCount + self.location = location + self.context = context + self.expandDistance = expandDistance + self.absoluteRect = absoluteRect + self.absoluteSize = absoluteSize + self.wallpaperNode = wallpaperNode + } + + static func ==(lhs: ChatOverscrollControl, rhs: ChatOverscrollControl) -> Bool { + if !lhs.backgroundColor.isEqual(rhs.backgroundColor) { + return false + } + if !lhs.foregroundColor.isEqual(rhs.foregroundColor) { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.unreadCount != rhs.unreadCount { + return false + } + if lhs.location != rhs.location { + return false + } if lhs.context !== rhs.context { return false } if lhs.expandDistance != rhs.expandDistance { return false } + if lhs.absoluteRect != rhs.absoluteRect { + return false + } + if lhs.absoluteSize != rhs.absoluteSize { + return false + } + if lhs.wallpaperNode !== rhs.wallpaperNode { + return false + } return true } static var body: Body { - let avatarBackground = Child(BlurredRoundedRectangle.self) - let avatarExpandProgress = Child(RadialProgressComponent.self) - let avatarCheck = Child(CheckComponent.self) - let avatar = Child(AvatarComponent.self) - let textBackground = Child(BlurredRoundedRectangle.self) - let text = Child(Text.self) + let contents = Child(OverscrollContentsComponent.self) return { context in - let text = text.update( - component: Text( - text: context.component.text, - font: Font.regular(12.0), - color: context.component.foregroundColor + let contents = contents.update( + component: OverscrollContentsComponent( + context: context.component.context, + backgroundColor: context.component.backgroundColor, + foregroundColor: context.component.foregroundColor, + peer: context.component.peer, + unreadCount: context.component.unreadCount, + location: context.component.location, + expandOffset: context.component.expandDistance, + absoluteRect: context.component.absoluteRect, + absoluteSize: context.component.absoluteSize, + wallpaperNode: context.component.wallpaperNode ), - availableSize: CGSize(width: context.availableSize.width, height: 100.0), + availableSize: context.availableSize, transition: context.transition ) - let textHorizontalPadding: CGFloat = 6.0 - let textVerticalPadding: CGFloat = 2.0 - let avatarSize: CGFloat = 48.0 - let avatarPadding: CGFloat = 8.0 - let avatarTextSpacing: CGFloat = 8.0 - let avatarProgressPadding: CGFloat = 2.5 + let size = CGSize(width: context.availableSize.width, height: contents.size.height) - let avatarBackgroundSize: CGFloat = context.component.peer != nil ? (avatarSize + avatarPadding * 2.0) : avatarSize - - let avatarBackground = avatarBackground.update( - component: BlurredRoundedRectangle( - color: context.component.backgroundColor - ), - availableSize: CGSize(width: avatarBackgroundSize, height: avatarBackgroundSize), - transition: context.transition - ) - - let avatarCheck = Condition(context.component.peer == nil, { () -> _UpdatedChildComponent in - let avatarCheckSize = avatarBackgroundSize + 2.0 - - return avatarCheck.update( - component: CheckComponent( - color: context.component.foregroundColor, - lineWidth: 2.5, - value: 1.0 - ), - availableSize: CGSize(width: avatarCheckSize, height: avatarCheckSize), - transition: context.transition - ) - }) - - let progressSize = avatarBackground.size.width - avatarProgressPadding * 2.0 - - let halfDistance = progressSize - let quarterDistance = halfDistance / 2.0 - - let clippedDistance = max(0.0, min(halfDistance * 2.0, context.component.expandDistance)) - - var mappedProgress: CGFloat - if clippedDistance <= quarterDistance { - mappedProgress = acos(1.0 - clippedDistance / quarterDistance) / (CGFloat.pi * 2.0) - } else if clippedDistance <= halfDistance { - let sectionDistance = halfDistance - clippedDistance - mappedProgress = 0.25 + asin(1.0 - sectionDistance / quarterDistance) / (CGFloat.pi * 2.0) - } else { - let restDistance = clippedDistance - halfDistance - mappedProgress = min(1.0, 0.5 + restDistance / 60.0) - } - mappedProgress = max(0.01, mappedProgress) - - let avatarExpandProgress = avatarExpandProgress.update( - component: RadialProgressComponent( - color: context.component.foregroundColor, - lineWidth: 2.5, - value: context.component.peer == nil ? 0.0 : mappedProgress - ), - availableSize: CGSize(width: progressSize, height: progressSize), - transition: context.transition - ) - - let textBackground = textBackground.update( - component: BlurredRoundedRectangle( - color: context.component.backgroundColor - ), - availableSize: CGSize(width: text.size.width + textHorizontalPadding * 2.0, height: text.size.height + textVerticalPadding * 2.0), - transition: context.transition - ) - - let size = CGSize(width: context.availableSize.width, height: avatarBackground.size.height + avatarTextSpacing + textBackground.size.height) - - let avatarBackgroundFrame = avatarBackground.size.topCentered(in: CGRect(origin: CGPoint(), size: size)) - - let avatar = context.component.peer.flatMap { peer in - avatar.update( - component: AvatarComponent( - context: context.component.context, - peer: peer - ), - availableSize: CGSize(width: avatarSize, height: avatarSize), - transition: context.transition - ) - } - - context.add(avatarBackground - .position(CGPoint( - x: avatarBackgroundFrame.midX, - y: avatarBackgroundFrame.midY - )) - ) - - if let avatarCheck = avatarCheck { - context.add(avatarCheck - .position(CGPoint( - x: avatarBackgroundFrame.midX, - y: avatarBackgroundFrame.midY - )) - ) - } - - context.add(avatarExpandProgress - .position(CGPoint( - x: avatarBackgroundFrame.midX, - y: avatarBackgroundFrame.midY - )) - ) - - if let avatar = avatar { - context.add(avatar - .position(CGPoint( - x: avatarBackgroundFrame.midX, - y: avatarBackgroundFrame.midY - )) - ) - } - - let textBackgroundFrame = textBackground.size.bottomCentered(in: CGRect(origin: CGPoint(), size: size)) - context.add(textBackground - .position(CGPoint( - x: textBackgroundFrame.midX, - y: textBackgroundFrame.midY - )) - ) - - let textFrame = text.size.centered(in: textBackgroundFrame) - context.add(text - .position(CGPoint( - x: textFrame.midX, - y: textFrame.midY - )) + context.add(contents + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) ) return size } } } + +final class ChatInputPanelOverscrollNode: ASDisplayNode { + let text: (String, [(Int, NSRange)]) + let priority: Int + private let titleNode: ImmediateTextNode + + init(text: (String, [(Int, NSRange)]), color: UIColor, priority: Int) { + self.text = text + self.priority = priority + self.titleNode = ImmediateTextNode() + + super.init() + + let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: color) + let bold = MarkdownAttributeSet(font: Font.bold(14.0), textColor: color) + + self.titleNode.attributedText = addAttributesToStringWithRanges(text, body: body, argumentAttributes: [0: bold]) + + self.addSubnode(self.titleNode) + } + + func update(size: CGSize) { + let titleSize = self.titleNode.updateLayout(size) + self.titleNode.frame = titleSize.centered(in: CGRect(origin: CGPoint(), size: size)) + } +} diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index 3122008f38..6402bb8cf9 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -60,6 +60,7 @@ final class ChatPanelInterfaceInteraction { let forwardSelectedMessages: () -> Void let forwardCurrentForwardMessages: () -> Void let forwardMessages: ([Message]) -> Void + let updateForwardMessageHideSendersNames: (Bool) -> Void let shareSelectedMessages: () -> Void let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void let updateInputModeAndDismissedButtonKeyboardMessageId: ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void @@ -144,6 +145,7 @@ final class ChatPanelInterfaceInteraction { forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, + updateForwardMessageHideSendersNames: @escaping (Bool) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, @@ -227,6 +229,7 @@ final class ChatPanelInterfaceInteraction { self.forwardSelectedMessages = forwardSelectedMessages self.forwardCurrentForwardMessages = forwardCurrentForwardMessages self.forwardMessages = forwardMessages + self.updateForwardMessageHideSendersNames = updateForwardMessageHideSendersNames self.shareSelectedMessages = shareSelectedMessages self.updateTextInputStateAndMode = updateTextInputStateAndMode self.updateInputModeAndDismissedButtonKeyboardMessageId = updateInputModeAndDismissedButtonKeyboardMessageId diff --git a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift index 2a5f31271e..0f259ab762 100644 --- a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift @@ -73,7 +73,7 @@ enum ChatMediaInputExpanded: Equatable { enum ChatInputMode: Equatable { case none case text - case media(mode: ChatMediaInputMode, expanded: ChatMediaInputExpanded?) + case media(mode: ChatMediaInputMode, expanded: ChatMediaInputExpanded?, focused: Bool) case inputButtons } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index e5ad7400dd..d801328eb3 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -65,6 +65,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, forwardSelectedMessages: { }, forwardCurrentForwardMessages: { }, forwardMessages: { _ in + }, updateForwardMessageHideSendersNames: { _ in }, shareSelectedMessages: { }, updateTextInputStateAndMode: { _ in }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in 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/ChatSendMessageActionSheetControllerNode.swift b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift index dac19b9c0f..784ef26bc8 100644 --- a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift @@ -267,7 +267,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, let outgoing: PresentationThemeBubbleColorComponents = self.presentationData.chatWallpaper.isEmpty ? self.presentationData.theme.chat.message.outgoing.bubble.withoutWallpaper : self.presentationData.theme.chat.message.outgoing.bubble.withWallpaper let maxCornerRadius = self.presentationData.chatBubbleCorners.mainRadius - self.messageBackgroundNode.image = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: maxCornerRadius, incoming: false, fillColor: outgoing.gradientFill, strokeColor: outgoing.fill == outgoing.gradientFill ? outgoing.stroke : .clear, neighbors: .none, theme: self.presentationData.theme.chat, wallpaper: self.presentationData.chatWallpaper, knockout: false) + self.messageBackgroundNode.image = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: maxCornerRadius, incoming: false, fillColor: outgoing.fill.last ?? outgoing.fill[0], strokeColor: outgoing.fill.count > 1 ? outgoing.stroke : .clear, neighbors: .none, theme: self.presentationData.theme.chat, wallpaper: self.presentationData.chatWallpaper, knockout: false) self.view.addSubview(self.effectView) self.addSubnode(self.dimNode) @@ -370,7 +370,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, let outgoing: PresentationThemeBubbleColorComponents = self.presentationData.chatWallpaper.isEmpty ? self.presentationData.theme.chat.message.outgoing.bubble.withoutWallpaper : self.presentationData.theme.chat.message.outgoing.bubble.withWallpaper let maxCornerRadius = self.presentationData.chatBubbleCorners.mainRadius - self.messageBackgroundNode.image = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: maxCornerRadius, incoming: false, fillColor: outgoing.gradientFill, strokeColor: outgoing.fill == outgoing.gradientFill ? outgoing.stroke : .clear, neighbors: .none, theme: self.presentationData.theme.chat, wallpaper: self.presentationData.chatWallpaper, knockout: false) + self.messageBackgroundNode.image = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: maxCornerRadius, incoming: false, fillColor: outgoing.fill.last ?? outgoing.fill[0], strokeColor: outgoing.fill.count > 1 ? outgoing.stroke : .clear, neighbors: .none, theme: self.presentationData.theme.chat, wallpaper: self.presentationData.chatWallpaper, knockout: false) for node in self.contentNodes { node.updateTheme(presentationData.theme) diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index 6e8abf40c7..310b9515f8 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -128,7 +128,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode { transition.updateFrame(node: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size)) var expanded = false - if case let .media(_, maybeExpanded) = interfaceState.inputMode, maybeExpanded != nil { + if case let .media(_, maybeExpanded, _) = interfaceState.inputMode, maybeExpanded != nil { expanded = true } transition.updateSublayerTransformScale(node: self.expandMediaInputButton, scale: CGPoint(x: 1.0, y: expanded ? 1.0 : -1.0)) diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index d54e49846a..499549bcae 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -230,6 +230,7 @@ enum ChatTextInputPanelPasteData { } class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { + let clippingNode: ASDisplayNode var textPlaceholderNode: ImmediateTextNode var contextPlaceholderNode: TextNode? var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode? @@ -434,6 +435,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) { self.presentationInterfaceState = presentationInterfaceState + self.clippingNode = ASDisplayNode() + self.clippingNode.clipsToBounds = true + self.textInputContainerBackgroundNode = ASImageNode() self.textInputContainerBackgroundNode.isUserInteractionEnabled = false self.textInputContainerBackgroundNode.displaysAsynchronously = false @@ -476,6 +480,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { super.init() + self.addSubnode(self.clippingNode) + self.menuButton.addTarget(self, action: #selector(self.menuButtonPressed), forControlEvents: .touchUpInside) self.menuButton.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { @@ -565,24 +571,24 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.searchLayoutClearButton.addTarget(self, action: #selector(self.searchLayoutClearButtonPressed), for: .touchUpInside) self.searchLayoutClearButton.alpha = 0.0 - self.addSubnode(self.textInputContainer) - self.addSubnode(self.textInputBackgroundNode) + self.clippingNode.addSubnode(self.textInputContainer) + self.clippingNode.addSubnode(self.textInputBackgroundNode) - self.addSubnode(self.textPlaceholderNode) + self.clippingNode.addSubnode(self.textPlaceholderNode) self.menuButton.addSubnode(self.menuButtonBackgroundNode) self.menuButton.addSubnode(self.menuButtonClippingNode) self.menuButtonClippingNode.addSubnode(self.menuButtonTextNode) self.menuButton.addSubnode(self.menuButtonIconNode) - self.addSubnode(self.menuButton) - self.addSubnode(self.attachmentButton) - self.addSubnode(self.attachmentButtonDisabledNode) + self.clippingNode.addSubnode(self.menuButton) + self.clippingNode.addSubnode(self.attachmentButton) + self.clippingNode.addSubnode(self.attachmentButtonDisabledNode) - self.addSubnode(self.actionButtons) - self.addSubnode(self.counterTextNode) + self.clippingNode.addSubnode(self.actionButtons) + self.clippingNode.addSubnode(self.counterTextNode) - self.view.addSubview(self.searchLayoutClearButton) + self.clippingNode.view.addSubview(self.searchLayoutClearButton) self.textInputBackgroundNode.clipsToBounds = true let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:))) @@ -1114,7 +1120,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { audioRecordingInfoContainerNode = ASDisplayNode() self.audioRecordingInfoContainerNode = audioRecordingInfoContainerNode - self.insertSubnode(audioRecordingInfoContainerNode, at: 0) + self.clippingNode.insertSubnode(audioRecordingInfoContainerNode, at: 0) } var animateTimeSlideIn = false @@ -1142,7 +1148,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self?.interfaceInteraction?.finishMediaRecording(.dismiss) }) self.audioRecordingCancelIndicator = audioRecordingCancelIndicator - self.insertSubnode(audioRecordingCancelIndicator, at: 0) + self.clippingNode.insertSubnode(audioRecordingCancelIndicator, at: 0) } let isLocked = mediaRecordingState?.isLocked ?? (interfaceState.recordedMediaPreview != nil) @@ -1251,7 +1257,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { audioRecordingDotNode = AnimationNode(animation: "BinRed") self.audioRecordingDotNode = audioRecordingDotNode self.audioRecordingDotNodeDismissed = false - self.insertSubnode(audioRecordingDotNode, belowSubnode: self.menuButton) + self.clippingNode.insertSubnode(audioRecordingDotNode, belowSubnode: self.menuButton) self.animatingBinNode?.removeFromSupernode() self.animatingBinNode = nil } @@ -1406,7 +1412,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self?.interfaceInteraction?.finishMediaRecording(.send) return true } - self.insertSubnode(mediaRecordingAccessibilityArea, aboveSubnode: self.actionButtons) + self.clippingNode.insertSubnode(mediaRecordingAccessibilityArea, aboveSubnode: self.actionButtons) } self.actionButtons.isAccessibilityElement = false let size: CGFloat = 120.0 @@ -1469,7 +1475,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { contextPlaceholderNode.displaysAsynchronously = false contextPlaceholderNode.isUserInteractionEnabled = false self.contextPlaceholderNode = contextPlaceholderNode - self.insertSubnode(contextPlaceholderNode, aboveSubnode: self.textPlaceholderNode) + self.clippingNode.insertSubnode(contextPlaceholderNode, aboveSubnode: self.textPlaceholderNode) } let _ = placeholderApply() @@ -1489,7 +1495,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { slowmodePlaceholderNode = ChatTextInputSlowmodePlaceholderNode(theme: interfaceState.theme) self.slowmodePlaceholderNode = slowmodePlaceholderNode - self.insertSubnode(slowmodePlaceholderNode, aboveSubnode: self.textPlaceholderNode) + self.clippingNode.insertSubnode(slowmodePlaceholderNode, aboveSubnode: self.textPlaceholderNode) } let placeholderFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: CGSize(width: width - leftInset - rightInset - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: 30.0)) slowmodePlaceholderNode.updateState(slowmodeState) @@ -1521,7 +1527,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { button.updateLayout(size: buttonSize) let buttonFrame = CGRect(origin: CGPoint(x: nextButtonTopRight.x - buttonSize.width, y: nextButtonTopRight.y + floor((minimalInputHeight - buttonSize.height) / 2.0)), size: buttonSize) if button.supernode == nil { - self.addSubnode(button) + self.clippingNode.addSubnode(button) button.frame = buttonFrame.offsetBy(dx: -additionalOffset, dy: 0.0) transition.updateFrame(layer: button.layer, frame: buttonFrame) if animatedTransition { @@ -1645,6 +1651,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { prevPreviewInputPanelNode.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } + var clippingDelta: CGFloat = 0.0 + if case let .media(_, _, focused) = interfaceState.inputMode, focused { + clippingDelta = -panelHeight + } + transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))) + transition.updateSublayerTransformOffset(layer: self.clippingNode.layer, offset: CGPoint(x: 0.0, y: clippingDelta)) + return panelHeight } @@ -2248,11 +2261,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { @objc func expandButtonPressed() { self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in - if case let .media(mode, expanded) = state.inputMode { + if case let .media(mode, expanded, focused) = state.inputMode { if let _ = expanded { - return (.media(mode: mode, expanded: nil), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) + return (.media(mode: mode, expanded: nil, focused: focused), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) } else { - return (.media(mode: mode, expanded: .content), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) + return (.media(mode: mode, expanded: .content, focused: focused), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) } } else { return (state.inputMode, state.interfaceState.messageActionsState.closedButtonKeyboardMessageId) diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift new file mode 100644 index 0000000000..9a1958ba17 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -0,0 +1,928 @@ +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.dismiss() + strongSelf.completion(emoticon) + } + self.controllerNode.dismiss = { [weak self] in + self?.presentingViewController?.dismiss(animated: false, completion: nil) + } + self.controllerNode.cancel = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.dismiss() + strongSelf.previewTheme(nil) + } + } + + 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) + } +} + +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 + + 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) + + self.switchThemeButton = HighlightTrackingButtonNode() + 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) + 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.selectedEmoticon != emoticon { + strongSelf.animateCrossfade(updateSunIcon: true) + + 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) { + guard !self.animatedOut else { + return + } + 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)) + + 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() { + 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() { + 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) + } + + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if self.dismissByTapOutside, case .ended = recognizer.state { + self.cancelButtonPressed() + } + } + + 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) + + 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/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index d46fc70974..312e858394 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -341,6 +341,8 @@ final class ChatTitleView: UIView, NavigationBarTitleView { stringValue = strings.Activity_RecordingVideoMessage case .uploadingInstantVideo: stringValue = strings.Activity_UploadingVideoMessage + case .choosingSticker: + stringValue = strings.Activity_ChoosingSticker case .speakingInGroupCall: stringValue = "" } @@ -372,6 +374,8 @@ final class ChatTitleView: UIView, NavigationBarTitleView { state = .playingGame(string, color) case .speakingInGroupCall: state = .typingText(string, color) + case .choosingSticker: + state = .choosingSticker(string, color) } } else { if let titleContent = self.titleContent { diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 340b957869..6cc86fdb8a 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -224,6 +224,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue { strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( context: strongSelf.context, + highlightedPackId: nil, sendSticker: { fileReference, sourceNode, sourceRect in if let strongSelf = self { @@ -263,6 +264,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { }, navigateBackToStickers: { }, setGifMode: { _ in }, openSettings: { + }, openTrending: { _ in + }, dismissTrendingPacks: { _ in }, toggleSearch: { [weak self] value, searchMode, query in if let strongSelf = self { if let searchMode = searchMode, value { @@ -287,8 +290,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { if let strongSelf = self { strongSelf.controllerInteraction.updateInputMode { current in switch current { - case let .media(mode, _): - return .media(mode: mode, expanded: .search(searchMode)) + case let .media(mode, _, focused): + return .media(mode: mode, expanded: .search(searchMode), focused: focused) default: return current } @@ -299,8 +302,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { } else { strongSelf.controllerInteraction.updateInputMode { current in switch current { - case let .media(mode, _): - return .media(mode: mode, expanded: nil) + case let .media(mode, _, focused): + return .media(mode: mode, expanded: nil, focused: focused) default: return current } @@ -336,6 +339,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue { strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( context: strongSelf.context, + highlightedPackId: nil, sendSticker: { fileReference, sourceNode, sourceRect in if let strongSelf = self { @@ -375,6 +379,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { }, navigateBackToStickers: { }, setGifMode: { _ in }, openSettings: { + }, openTrending: { _ in + }, dismissTrendingPacks: { _ in }, toggleSearch: { [weak self] value, searchMode, query in if let strongSelf = self { if let searchMode = searchMode, value { @@ -399,8 +405,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { if let strongSelf = self { strongSelf.controllerInteraction.updateInputMode { current in switch current { - case let .media(mode, _): - return .media(mode: mode, expanded: .search(searchMode)) + case let .media(mode, _, focused): + return .media(mode: mode, expanded: .search(searchMode), focused: focused) default: return current } @@ -411,8 +417,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { } else { strongSelf.controllerInteraction.updateInputMode { current in switch current { - case let .media(mode, _): - return .media(mode: mode, expanded: nil) + case let .media(mode, _, focused): + return .media(mode: mode, expanded: nil, focused: focused) default: return current } @@ -582,20 +588,9 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { for info in view.collectionInfos { installedPacks.insert(info.0) } - - var hasUnreadTrending: Bool? - for pack in trendingPacks { - if hasUnreadTrending == nil { - hasUnreadTrending = false - } - if pack.unread { - hasUnreadTrending = true - break - } - } - - let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasUnreadTrending: nil, theme: theme, hasGifs: false, hasSettings: false) - let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasSearch: false, hasAccessories: false, strings: strings, theme: theme) + + let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, hasGifs: false, hasSettings: false) + let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: trendingPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme) let (previousPanelEntries, previousGridEntries) = previousStickerEntries.swap((panelEntries, gridEntries)) return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: stickersInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: stickersInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty) @@ -629,8 +624,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { installedPacks.insert(info.0) } - let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasUnreadTrending: nil, theme: theme, hasGifs: false, hasSettings: false) - let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasSearch: false, hasAccessories: false, strings: strings, theme: theme) + let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, hasGifs: false, hasSettings: false) + let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], hasSearch: false, hasAccessories: false, strings: strings, theme: theme) let (previousPanelEntries, previousGridEntries) = previousMaskEntries.swap((panelEntries, gridEntries)) return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: masksInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: masksInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty) diff --git a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift index 4025f3ecc8..660730c184 100644 --- a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift +++ b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift @@ -95,7 +95,7 @@ private final class FeaturedPackEntry: Identifiable, Comparable { func item(account: Account, interaction: FeaturedInteraction, isOther: Bool) -> GridItem { let info = self.info - return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: true, info: self.info, topItems: self.topItems, topSeparator: self.topSeparator, regularInsets: self.regularInsets, installed: self.installed, unread: self.unread, open: { + return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: true, fillsRow: false, info: self.info, topItems: self.topItems, topSeparator: self.topSeparator, regularInsets: self.regularInsets, installed: self.installed, unread: self.unread, open: { interaction.openPack(info) }, install: { interaction.installPack(info, !self.installed) @@ -153,16 +153,17 @@ private struct FeaturedTransition { let insertions: [GridNodeInsertItem] let updates: [GridNodeUpdateItem] let initial: Bool + let scrollToItem: GridNodeScrollToItem? } -private func preparedTransition(from fromEntries: [FeaturedEntry], to toEntries: [FeaturedEntry], account: Account, interaction: FeaturedInteraction, initial: Bool) -> FeaturedTransition { +private func preparedTransition(from fromEntries: [FeaturedEntry], to toEntries: [FeaturedEntry], account: Account, interaction: FeaturedInteraction, initial: Bool, scrollToItem: GridNodeScrollToItem?) -> FeaturedTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction), previousIndex: $0.2) } let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction)) } - return FeaturedTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial) + return FeaturedTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial, scrollToItem: scrollToItem) } private func featuredScreenEntries(featuredEntries: [FeaturedStickerPackItem], installedPacks: Set, theme: PresentationTheme, strings: PresentationStrings, fixedUnread: Set, additionalPacks: [FeaturedStickerPackItem]) -> [FeaturedEntry] { @@ -280,6 +281,10 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { }, openSettings: { }, + openTrending: { _ in + }, + dismissTrendingPacks: { _ in + }, toggleSearch: { _, _, _ in }, openPeerSpecificSettings: { @@ -360,6 +365,8 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { return (items, fixedUnread) } + let highlightedPackId = controller.highlightedPackId + self.disposable = (combineLatest(queue: .mainQueue(), mappedFeatured, self.additionalPacks.get(), @@ -378,7 +385,20 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { let entries = featuredScreenEntries(featuredEntries: featuredEntries.0, installedPacks: installedPacks, theme: presentationData.theme, strings: presentationData.strings, fixedUnread: featuredEntries.1, additionalPacks: additionalPacks) let previous = previousEntries.swap(entries) - return preparedTransition(from: previous ?? [], to: entries, account: context.account, interaction: interaction, initial: previous == nil) + var scrollToItem: GridNodeScrollToItem? + let initial = previous == nil + if initial, let highlightedPackId = highlightedPackId { + var index = 0 + for entry in entries { + if case let .pack(packEntry, _) = entry, packEntry.info.id == highlightedPackId { + scrollToItem = GridNodeScrollToItem(index: index, position: .center(0.0), transition: .immediate, directionHint: .down, adjustForSection: false) + break + } + index += 1 + } + } + + return preparedTransition(from: previous ?? [], to: entries, account: context.account, interaction: interaction, initial: initial, scrollToItem: scrollToItem) } |> deliverOnMainQueue).start(next: { [weak self] transition in guard let strongSelf = self else { @@ -682,7 +702,15 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { self.enqueuedTransitions.remove(at: 0) let itemTransition: ContainedViewLayoutTransition = .immediate - self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: nil, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, synchronousLoads: transition.initial), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, synchronousLoads: transition.initial), completion: { [weak self] _ in + if let strongSelf = self, transition.initial { + strongSelf.gridNode.forEachItemNode({ itemNode in + if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode, itemNode.item?.info.id == strongSelf.controller?.highlightedPackId { + itemNode.highlight() + } + }) + } + }) } } @@ -711,6 +739,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { final class FeaturedStickersScreen: ViewController { private let context: AccountContext + fileprivate let highlightedPackId: ItemCollectionId? private let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? private var controllerNode: FeaturedStickersScreenNode { @@ -727,8 +756,9 @@ final class FeaturedStickersScreen: ViewController { fileprivate var searchNavigationNode: SearchNavigationContentNode? - public init(context: AccountContext, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil) { + public init(context: AccountContext, highlightedPackId: ItemCollectionId?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil) { self.context = context + self.highlightedPackId = highlightedPackId self.sendSticker = sendSticker self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -956,7 +986,7 @@ private enum FeaturedSearchEntry: Identifiable, Comparable { interaction.sendSticker(.standalone(media: stickerItem.file), node, rect) }) case let .global(_, info, topItems, installed, topSeparator): - return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: { + return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: true, fillsRow: true, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: { interaction.open(info) }, install: { interaction.install(info, topItems, !installed) diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index e5945e9be1..111afb89c4 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -399,7 +399,7 @@ private func fetchCachedBlurredWallpaperRepresentation(resource: MediaResource, }) |> runOn(Queue.concurrentDefaultQueue()) } -public func fetchCachedSharedResourceRepresentation(accountManager: AccountManager, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal { +public func fetchCachedSharedResourceRepresentation(accountManager: AccountManager, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal { if let representation = representation as? CachedScaledImageRepresentation { return accountManager.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) |> mapToSignal { data -> Signal in diff --git a/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift index eab1c6d1ea..ea26f89429 100644 --- a/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ForwardAccessoryPanelNode.swift @@ -6,10 +6,13 @@ import Postbox import SwiftSignalKit import Display import TelegramPresentationData +import TelegramUIPreferences import AccountContext import LocalizedPeerData import AlertUI import PresentationDataUtils +import TextFormat +import Markdown func textStringForForwardedMessage(_ message: Message, strings: PresentationStrings) -> (String, Bool) { for media in message.media { @@ -76,6 +79,8 @@ func textStringForForwardedMessage(_ message: Message, strings: PresentationStri final class ForwardAccessoryPanelNode: AccessoryPanelNode { private let messageDisposable = MetaDisposable() let messageIds: [MessageId] + private var authors: String? + private var sourcePeer: (isPersonal: Bool, displayTitle: String)? let closeButton: ASButtonNode let lineNode: ASImageNode @@ -87,14 +92,20 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { let context: AccountContext var theme: PresentationTheme var strings: PresentationStrings - + var fontSize: PresentationFontSize + var nameDisplayOrder: PresentationPersonNameOrder + var hideSendersNames: Bool + private var validLayout: (size: CGSize, interfaceState: ChatPresentationInterfaceState)? - init(context: AccountContext, messageIds: [MessageId], theme: PresentationTheme, strings: PresentationStrings) { + init(context: AccountContext, messageIds: [MessageId], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, nameDisplayOrder: PresentationPersonNameOrder, hideSendersNames: Bool) { self.context = context self.messageIds = messageIds self.theme = theme self.strings = strings + self.fontSize = fontSize + self.nameDisplayOrder = nameDisplayOrder + self.hideSendersNames = hideSendersNames self.closeButton = ASButtonNode() self.closeButton.accessibilityLabel = strings.VoiceOver_DiscardPreparedContent @@ -133,6 +144,7 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { var authors = "" var uniquePeerIds = Set() var text = "" + var sourcePeer: (Bool, String)? for message in messages { if let author = message.effectiveAuthor, !uniquePeerIds.contains(author.id) { uniquePeerIds.insert(author.id) @@ -141,6 +153,9 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { } authors.append(author.compactDisplayTitle) } + if let peer = message.peers[message.id.peerId] { + sourcePeer = (peer.id.namespace == Namespaces.Peer.CloudUser, peer.displayTitle(strings: strongSelf.strings, displayOrder: strongSelf.nameDisplayOrder)) + } } if messages.count == 1 { let (string, _) = textStringForForwardedMessage(messages[0], strings: strings) @@ -149,7 +164,15 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { text = strings.ForwardedMessages(Int32(messages.count)) } - strongSelf.titleNode.attributedText = NSAttributedString(string: authors, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor) + strongSelf.sourcePeer = sourcePeer + strongSelf.authors = authors + + if strongSelf.hideSendersNames { + strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.strings.Conversation_ForwardOptions_You, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor) + } else { + strongSelf.titleNode.attributedText = NSAttributedString(string: authors, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor) + } + strongSelf.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor) let headerString: String @@ -178,20 +201,27 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { } override func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - if self.theme !== theme || self.strings !== strings { + self.updateThemeAndStrings(theme: theme, strings: strings, hideSendersNames: self.hideSendersNames) + } + + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, hideSendersNames: Bool) { + if self.theme !== theme || self.strings !== strings || self.hideSendersNames != hideSendersNames { self.theme = theme self.strings = strings + self.hideSendersNames = hideSendersNames self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: []) self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme) - if let text = self.titleNode.attributedText?.string { - self.titleNode.attributedText = NSAttributedString(string: text, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor) + if hideSendersNames { + self.titleNode.attributedText = NSAttributedString(string: strings.Conversation_ForwardOptions_You, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor) + } else if let authors = self.authors { + self.titleNode.attributedText = NSAttributedString(string: authors, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor) } if let text = self.textNode.attributedText?.string { - self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.primaryTextColor) + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.secondaryTextColor) } if let (size, interfaceState) = self.validLayout { @@ -208,14 +238,16 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { self.validLayout = (size, interfaceState) let bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 45.0)) - let leftInset: CGFloat = 55.0 + let inset: CGFloat = interfaceState.renderedPeer == nil ? 19.0 : 55.0 + let leftInset: CGFloat = inset + let rightInset: CGFloat = inset let textLineInset: CGFloat = 10.0 - let rightInset: CGFloat = 55.0 let textRightInset: CGFloat = 20.0 let closeButtonSize = CGSize(width: 44.0, height: bounds.height) let closeButtonFrame = CGRect(origin: CGPoint(x: bounds.width - rightInset - closeButtonSize.width + 12.0, y: 2.0), size: closeButtonSize) self.closeButton.frame = closeButtonFrame + self.closeButton.isHidden = interfaceState.renderedPeer == nil self.actionArea.frame = CGRect(origin: CGPoint(x: leftInset, y: 2.0), size: CGSize(width: closeButtonFrame.minX - leftInset, height: bounds.height)) @@ -229,9 +261,28 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { } @objc func closePressed() { - let alertController = textAlertController(context: self.context, title: self.strings.Conversation_CancelForwardTitle, text: self.strings.Conversation_CancelForwardText, actions: [TextAlertAction(type: .genericAction, title: self.strings.Conversation_CancelForwardSelectChat, action: { [weak self] in + guard let (isPersonal, peerDisplayTitle) = self.sourcePeer else { + return + } + let messageCount = Int32(self.messageIds.count) + let messages = self.strings.Conversation_ForwardOptions_Messages(messageCount) + let string = isPersonal ? self.strings.Conversation_ForwardOptions_TextPersonal(messages, peerDisplayTitle) : self.strings.Conversation_ForwardOptions_Text(messages, peerDisplayTitle) + + let font = Font.regular(floor(self.fontSize.baseDisplaySize * 15.0 / 17.0)) + let boldFont = Font.semibold(floor(self.fontSize.baseDisplaySize * 15.0 / 17.0)) + let body = MarkdownAttributeSet(font: font, textColor: self.theme.actionSheet.secondaryTextColor) + let bold = MarkdownAttributeSet(font: boldFont, textColor: self.theme.actionSheet.secondaryTextColor) + + let title = NSAttributedString(string: self.strings.Conversation_ForwardOptions_Title(messageCount), font: Font.semibold(floor(self.fontSize.baseDisplaySize)), textColor: self.theme.actionSheet.primaryTextColor, paragraphAlignment: .center) + let text = addAttributesToStringWithRanges(string._tuple, body: body, argumentAttributes: [0: bold, 1: bold], textAlignment: .center) + + let alertController = richTextAlertController(context: self.context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: self.strings.Conversation_ForwardOptions_ForwardToAnotherChat, action: { [weak self] in self?.interfaceInteraction?.forwardCurrentForwardMessages() - }), TextAlertAction(type: .defaultAction, title: self.strings.Conversation_CancelForwardCancelForward, action: { [weak self] in + }), TextAlertAction(type: .genericAction, title: self.hideSendersNames ? self.strings.Conversation_ForwardOptions_ShowSendersNames : self.strings.Conversation_ForwardOptions_HideSendersNames, action: { [weak self] in + if let strongSelf = self { + strongSelf.interfaceInteraction?.updateForwardMessageHideSendersNames(!strongSelf.hideSendersNames) + } + }), TextAlertAction(type: .destructiveAction, title: self.strings.Conversation_ForwardOptions_CancelForwarding, action: { [weak self] in self?.dismiss?() })], actionLayout: .vertical) self.interfaceInteraction?.presentController(alertController, nil) @@ -239,7 +290,11 @@ final class ForwardAccessoryPanelNode: AccessoryPanelNode { @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - self.interfaceInteraction?.forwardCurrentForwardMessages() + if self.closeButton.isHidden { + self.interfaceInteraction?.updateForwardMessageHideSendersNames(!self.hideSendersNames) + } else { + self.closePressed() + } } } } diff --git a/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift b/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift index dcf97d441c..b6863bb552 100644 --- a/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift +++ b/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift @@ -64,7 +64,7 @@ private func accountInfo(account: Account) -> Signal } } -func sharedAccountInfos(accountManager: AccountManager, accounts: Signal<[Account], NoError>) -> Signal { +func sharedAccountInfos(accountManager: AccountManager, accounts: Signal<[Account], NoError>) -> Signal { return combineLatest(accountManager.sharedData(keys: [SharedDataKeys.proxySettings]), accounts) |> take(1) |> mapToSignal { sharedData, accounts -> Signal in diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index 3f1d6a3a3b..0b94cf60fd 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -60,7 +60,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { private let queue = Queue.mainQueue() - private let accountManager: AccountManager + private let accountManager: AccountManager private let inForeground: Signal private let presentationData: Signal @@ -188,7 +188,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { public let galleryHiddenMediaManager: GalleryHiddenMediaManager = GalleryHiddenMediaManagerImpl() - init(accountManager: AccountManager, inForeground: Signal, presentationData: Signal) { + init(accountManager: AccountManager, inForeground: Signal, presentationData: Signal) { self.accountManager = accountManager self.inForeground = inForeground self.presentationData = presentationData 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/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index fa663977fc..7389aea8f2 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -94,7 +94,7 @@ public final class NotificationViewControllerImpl { if sharedAccountContext == nil { initializeAccountManagement() - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false) + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false) var initialPresentationDataAndSettings: InitialPresentationDataAndSettings? let semaphore = DispatchSemaphore(value: 0) 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/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index ff61025692..84c8380770 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -388,7 +388,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur navigationController?.pushViewController(previewController) } } else if let settings = dataAndTheme.1 { - if let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors.flatMap { (UIColor(argb: $0.top), UIColor(argb: $0.bottom)) }, wallpaper: settings.wallpaper) { + if let theme = makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors, wallpaper: settings.wallpaper) { let previewController = ThemePreviewController(context: context, previewTheme: theme, source: .theme(dataAndTheme.2)) navigationController?.pushViewController(previewController) } diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index fdb8cfb908..5114643757 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._internalFromInt64Value(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 f392f8b9e5..56d805c414 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -186,7 +186,8 @@ 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() @@ -201,7 +202,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu } } - self.historyNode.endedInteractiveDragging = { [weak self] in + self.historyNode.endedInteractiveDragging = { [weak self] _ in guard let strongSelf = self else { return } @@ -527,7 +528,8 @@ 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 historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in @@ -613,7 +615,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu } } - self.historyNode.endedInteractiveDragging = { [weak self] in + self.historyNode.endedInteractiveDragging = { [weak self] _ in guard let strongSelf = self else { return } diff --git a/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift b/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift index 2f648a2f48..e17807d601 100644 --- a/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift +++ b/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift @@ -86,7 +86,7 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, pres } final class OverlayPlayerControlsNode: ASDisplayNode { - private let accountManager: AccountManager + private let accountManager: AccountManager private let postbox: Postbox private var presentationData: PresentationData @@ -153,7 +153,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat)? - init(account: Account, accountManager: AccountManager, presentationData: PresentationData, status: Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError>) { + init(account: Account, accountManager: AccountManager, presentationData: PresentationData, status: Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError>) { self.accountManager = accountManager self.postbox = account.postbox self.presentationData = presentationData diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index c5bd2ad74a..f90d3f1a32 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -59,7 +59,8 @@ 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 49229347a9..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) @@ -379,6 +379,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { forwardMessages() }, forwardCurrentForwardMessages: { }, forwardMessages: { _ in + }, updateForwardMessageHideSendersNames: { _ in }, shareSelectedMessages: { shareMessages() }, updateTextInputStateAndMode: { _ in @@ -1570,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 @@ -3145,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) } } @@ -3156,7 +3157,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD break } }, sendFile: nil, - sendSticker: { f, sourceNode, sourceRect in + sendSticker: { _, _, _ in return false }, requestMessageActionUrlAuth: nil, joinVoiceChat: { peerId, invite, call in @@ -3219,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) } } @@ -3483,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) @@ -3608,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) @@ -3793,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 @@ -4775,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) } } @@ -5496,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) @@ -5644,7 +5670,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD func forwardMessages(messageIds: Set?) { if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true)) - peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode in + peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, hideSendersNames in guard let strongSelf = self, let strongController = peerSelectionController else { return } @@ -5665,8 +5691,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } } + var attributes: [MessageAttribute] = [] + if hideSendersNames { + attributes.append(ForwardHideSendersNamesMessageAttribute()) + } + result.append(contentsOf: messageIds.map { messageId -> EnqueueMessage in - return .forward(source: messageId, grouping: .auto, attributes: [], correlationId: nil) + return .forward(source: messageId, grouping: .auto, attributes: attributes, correlationId: nil) }) var displayPeers: [Peer] = [] @@ -6048,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 } @@ -6446,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)])>() @@ -6468,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 @@ -6478,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( @@ -6701,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/PeerSelectionController.swift b/submodules/TelegramUI/Sources/PeerSelectionController.swift index 2873c2ae74..9cc6ab141b 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionController.swift @@ -19,7 +19,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private var customTitle: String? public var peerSelected: ((Peer) -> Void)? - public var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode) -> Void)? + public var multiplePeersSelected: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode, Bool) -> Void)? private let filter: ChatListNodePeersFilter private let attemptSelection: ((Peer) -> Void)? @@ -58,7 +58,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private let hasContactSelector: Bool private let hasGlobalSearch: Bool private let pretendPresentedInModal: Bool - private let forwardedMessagesCount: Int + private let forwardedMessageIds: [EngineMessage.Id] override public var _presentedInModal: Bool { get { @@ -86,7 +86,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.attemptSelection = params.attemptSelection self.createNewGroup = params.createNewGroup self.pretendPresentedInModal = params.pretendPresentedInModal - self.forwardedMessagesCount = params.forwardedMessagesCount + self.forwardedMessageIds = params.forwardedMessageIds super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -150,7 +150,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } override public func loadDisplayNode() { - self.displayNode = PeerSelectionControllerNode(context: self.context, filter: self.filter, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessagesCount: self.forwardedMessagesCount, createNewGroup: self.createNewGroup, present: { [weak self] c, a in + self.displayNode = PeerSelectionControllerNode(context: self.context, filter: self.filter, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, createNewGroup: self.createNewGroup, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }, presentInGlobalOverlay: { [weak self] c, a in self?.presentInGlobalOverlay(c, with: a) @@ -160,8 +160,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.peerSelectionNode.navigationBar = self.navigationBar - self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode in - self?.multiplePeersSelected?(peers, peerMap, text, mode) + self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, hideSendersNames in + self?.multiplePeersSelected?(peers, peerMap, text, mode, hideSendersNames) } self.peerSelectionNode.requestDeactivateSearch = { [weak self] in diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index d2eb9bf39a..68f30d4f3e 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -20,6 +20,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let dismiss: () -> Void private let filter: ChatListNodePeersFilter private let hasGlobalSearch: Bool + private let forwardedMessageIds: [EngineMessage.Id] private var presentationInterfaceState: ChatPresentationInterfaceState private var interfaceInteraction: ChatPanelInterfaceInteraction? @@ -37,6 +38,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let segmentedControlNode: SegmentedControlNode? private var textInputPanelNode: PeerSelectionTextInputPanelNode? + private var forwardAccessoryPanelNode: ForwardAccessoryPanelNode? var contactListNode: ContactListNode? let chatListNode: ChatListNode @@ -56,7 +58,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { var requestOpenDisabledPeer: ((Peer) -> Void)? var requestOpenPeerFromSearch: ((Peer) -> Void)? var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)? - var requestSend: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode) -> Void)? + var requestSend: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode, Bool) -> Void)? private var presentationData: PresentationData private var presentationDataDisposable: Disposable? @@ -66,28 +68,21 @@ final class PeerSelectionControllerNode: ASDisplayNode { return self.readyValue.get() } - init(context: AccountContext, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessagesCount: Int, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { + init(context: AccountContext, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { self.context = context self.present = present self.presentInGlobalOverlay = presentInGlobalOverlay self.dismiss = dismiss self.filter = filter self.hasGlobalSearch = hasGlobalSearch + self.forwardedMessageIds = forwardedMessageIds let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = presentationData self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil) - var mockMessageIds: [MessageId]? - if forwardedMessagesCount > 0 { - var messageIds: [MessageId] = [] - for _ in 0 ..< forwardedMessagesCount { - messageIds.append(MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: Int32.random(in: 0 ..< Int32.max))) - } - mockMessageIds = messageIds - } - self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState { $0.withUpdatedForwardMessageIds(mockMessageIds) } + self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState { $0.withUpdatedForwardMessageIds(forwardedMessageIds) } if hasChatListSelector && hasContactSelector { self.toolbarBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor) @@ -198,6 +193,10 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, forwardSelectedMessages: { }, forwardCurrentForwardMessages: { }, forwardMessages: { _ in + }, updateForwardMessageHideSendersNames: { [weak self] value in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageHideSendersNames(value) }) }) + } }, shareSelectedMessages: { }, updateTextInputStateAndMode: { [weak self] f in if let strongSelf = self { @@ -335,6 +334,11 @@ final class PeerSelectionControllerNode: ASDisplayNode { func beginSelection() { if let _ = self.textInputPanelNode { } else { + let forwardAccessoryPanelNode = ForwardAccessoryPanelNode(context: self.context, messageIds: self.forwardedMessageIds, theme: self.presentationData.theme, strings: self.presentationData.strings, fontSize: self.presentationData.chatFontSize, nameDisplayOrder: self.presentationData.nameDisplayOrder, hideSendersNames: false) + forwardAccessoryPanelNode.interfaceInteraction = self.interfaceInteraction + self.addSubnode(forwardAccessoryPanelNode) + self.forwardAccessoryPanelNode = forwardAccessoryPanelNode + let textInputPanelNode = PeerSelectionTextInputPanelNode(presentationInterfaceState: self.presentationInterfaceState, presentController: { [weak self] c in self?.present(c, nil) }) textInputPanelNode.interfaceInteraction = self.interfaceInteraction textInputPanelNode.sendMessage = { [weak self] mode in @@ -342,10 +346,13 @@ final class PeerSelectionControllerNode: ASDisplayNode { return } + let effectiveInputText = strongSelf.presentationInterfaceState.interfaceState.composeInputState.inputText + let hideSendersNames = strongSelf.presentationInterfaceState.interfaceState.forwardMessageHideSendersNames + if strongSelf.contactListActive { strongSelf.contactListNode?.multipleSelection = true let selectedContactPeers = strongSelf.contactListNode?.selectedPeers ?? [] - let effectiveInputText = strongSelf.presentationInterfaceState.interfaceState.composeInputState.inputText + var selectedPeers: [Peer] = [] var selectedPeerMap: [PeerId: Peer] = [:] for contactPeer in selectedContactPeers { @@ -355,7 +362,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } } if !selectedPeers.isEmpty { - strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode) + strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, hideSendersNames) } } else { var selectedPeerIds: [PeerId] = [] @@ -366,14 +373,13 @@ final class PeerSelectionControllerNode: ASDisplayNode { return state } if !selectedPeerIds.isEmpty { - let effectiveInputText = strongSelf.presentationInterfaceState.interfaceState.composeInputState.inputText var selectedPeers: [Peer] = [] for peerId in selectedPeerIds { if let peer = selectedPeerMap[peerId] { selectedPeers.append(peer) } } - strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode) + strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, hideSendersNames) } } } @@ -416,6 +422,12 @@ final class PeerSelectionControllerNode: ASDisplayNode { var toolbarHeight: CGFloat = cleanInsets.bottom var textPanelHeight: CGFloat? + var accessoryHeight: CGFloat = 0.0 + + if let forwardAccessoryPanelNode = self.forwardAccessoryPanelNode { + let size = forwardAccessoryPanelNode.calculateSizeThatFits(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)) + accessoryHeight = size.height + } if let textInputPanelNode = self.textInputPanelNode { var panelTransition = transition @@ -433,15 +445,30 @@ final class PeerSelectionControllerNode: ASDisplayNode { let panelFrame = CGRect(x: 0.0, y: layout.size.height - panelHeight, width: layout.size.width, height: panelHeight) if textInputPanelNode.frame.width.isZero { var initialPanelFrame = panelFrame - initialPanelFrame.origin.y = layout.size.height + initialPanelFrame.origin.y = layout.size.height + accessoryHeight textInputPanelNode.frame = initialPanelFrame } transition.updateFrame(node: textInputPanelNode, frame: panelFrame) } + if let forwardAccessoryPanelNode = self.forwardAccessoryPanelNode { + let size = forwardAccessoryPanelNode.calculateSizeThatFits(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)) + forwardAccessoryPanelNode.updateState(size: size, interfaceState: self.presentationInterfaceState) + forwardAccessoryPanelNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, hideSendersNames: self.presentationInterfaceState.interfaceState.forwardMessageHideSendersNames) + let panelFrame = CGRect(x: layout.safeInsets.left, y: layout.size.height - (textPanelHeight ?? 0.0) - size.height, width: size.width - layout.safeInsets.left - layout.safeInsets.right, height: size.height) + + accessoryHeight = size.height + if forwardAccessoryPanelNode.frame.width.isZero { + var initialPanelFrame = panelFrame + initialPanelFrame.origin.y = layout.size.height + forwardAccessoryPanelNode.frame = initialPanelFrame + } + transition.updateFrame(node: forwardAccessoryPanelNode, frame: panelFrame) + } + if let segmentedControlNode = self.segmentedControlNode, let toolbarBackgroundNode = self.toolbarBackgroundNode, let toolbarSeparatorNode = self.toolbarSeparatorNode { if let textPanelHeight = textPanelHeight { - toolbarHeight = textPanelHeight + toolbarHeight = textPanelHeight + accessoryHeight } else { toolbarHeight += 44.0 } diff --git a/submodules/TelegramUI/Sources/PollResultsController.swift b/submodules/TelegramUI/Sources/PollResultsController.swift index c90f551de8..a6e213ab76 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/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index 383a66ef53..08dcbcd4dd 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -71,7 +71,7 @@ private final class PrefetchManagerInnerImpl { } } - let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"] + let popularEmoji = ["\u{2764}", "👍", "👎", "😳", "😒", "🥳", "😡", "😮", "😂", "😘", "😍", "🙄", "😎"] for emoji in popularEmoji { if let sticker = animatedEmojiStickers[emoji] { if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) { diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index 7db09e7585..559013266f 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -205,7 +205,7 @@ public class ShareRootControllerImpl { let internalContext: InternalContext - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false) + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true, isReadOnly: false) if let globalInternalContext = globalInternalContext { internalContext = globalInternalContext diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 5ab415779d..46928694bf 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -57,7 +57,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { public let applicationBindings: TelegramApplicationBindings public let sharedContainerPath: String public let basePath: String - public let accountManager: AccountManager + public let accountManager: AccountManager public let appLockContext: AppLockContext private let navigateToChatImpl: (AccountRecordId, PeerId, MessageId?) -> Void @@ -161,7 +161,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { private var spotlightDataContext: SpotlightDataContext? private var widgetDataContext: WidgetDataContext? - public init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal, voipNotificationToken: Signal, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }) { + public init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal, voipNotificationToken: Signal, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }) { assert(Queue.mainQueue().isCurrent()) precondition(!testHasInstance) @@ -345,13 +345,17 @@ public final class SharedAccountContextImpl: SharedAccountContext { var result: [AccountRecordId: AccountAttributes] = [:] for record in view.records { let isLoggedOut = record.attributes.contains(where: { attribute in - return attribute is LoggedOutAccountAttribute + if case .loggedOut = attribute { + return true + } else { + return false + } }) if isLoggedOut { continue } let isTestingEnvironment = record.attributes.contains(where: { attribute in - if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment { + if case let .environment(environment) = attribute, case .test = environment.environment { return true } else { return false @@ -360,17 +364,17 @@ public final class SharedAccountContextImpl: SharedAccountContext { var backupData: AccountBackupData? var sortIndex: Int32 = 0 for attribute in record.attributes { - if let attribute = attribute as? AccountSortOrderAttribute { - sortIndex = attribute.order - } else if let attribute = attribute as? AccountBackupDataAttribute { - backupData = attribute.data + if case let .sortOrder(sortOrder) = attribute { + sortIndex = sortOrder.order + } else if case let .backupData(backupDataValue) = attribute { + backupData = backupDataValue.data } } result[record.id] = AccountAttributes(sortIndex: sortIndex, isTestingEnvironment: isTestingEnvironment, backupData: backupData) } let authRecord: (AccountRecordId, Bool)? = view.currentAuthAccount.flatMap({ authAccount in let isTestingEnvironment = authAccount.attributes.contains(where: { attribute in - if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment { + if case let .environment(environment) = attribute, case .test = environment.environment { return true } else { return false @@ -808,8 +812,14 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let record = record else { return nil } - var attributes = record.attributes.filter({ !($0 is AccountBackupDataAttribute) }) - attributes.append(AccountBackupDataAttribute(data: backupData)) + var attributes: [TelegramAccountManagerTypes.Attribute] = record.attributes.filter { attribute in + if case .backupData = attribute { + return false + } else { + return true + } + } + attributes.append(.backupData(AccountBackupDataAttribute(data: backupData))) return AccountRecord(id: record.id, attributes: attributes, temporarySessionId: record.temporarySessionId) }) } @@ -921,7 +931,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func beginNewAuth(testingEnvironment: Bool) { let _ = self.accountManager.transaction({ transaction -> Void in - let _ = transaction.createAuth([AccountEnvironmentAttribute(environment: testingEnvironment ? .test : .production)]) + let _ = transaction.createAuth([.environment(AccountEnvironmentAttribute(environment: testingEnvironment ? .test : .production))]) }).start() } @@ -1094,8 +1104,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 } @@ -1410,11 +1420,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] = [] @@ -1429,9 +1439,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/SharedMediaPlayer.swift b/submodules/TelegramUI/Sources/SharedMediaPlayer.swift index 2e2c8bdc09..d235274ce2 100644 --- a/submodules/TelegramUI/Sources/SharedMediaPlayer.swift +++ b/submodules/TelegramUI/Sources/SharedMediaPlayer.swift @@ -221,14 +221,7 @@ final class SharedMediaPlayer { if case .music = playbackData.type { rateValue = 1.0 } else { - switch strongSelf.playbackRate { - case .x1: - rateValue = 1.0 - case .x2: - rateValue = 1.8 - default: - rateValue = 1.0 - } + rateValue = strongSelf.playbackRate.doubleValue } switch playbackData.type { diff --git a/submodules/TelegramUI/Sources/SharedNotificationManager.swift b/submodules/TelegramUI/Sources/SharedNotificationManager.swift index 43f6907b46..51118a6d08 100644 --- a/submodules/TelegramUI/Sources/SharedNotificationManager.swift +++ b/submodules/TelegramUI/Sources/SharedNotificationManager.swift @@ -40,7 +40,7 @@ public final class SharedNotificationManager { private var inForeground: Bool = false private var inForegroundDisposable: Disposable? - private var accountManager: AccountManager? + private var accountManager: AccountManager? private var accountsAndKeys: [(Account, Bool, MasterNotificationKey)]? private var accountsAndKeysDisposable: Disposable? diff --git a/submodules/TelegramUI/Sources/SpotlightContacts.swift b/submodules/TelegramUI/Sources/SpotlightContacts.swift index d1e97aeb19..1ad6e86fbc 100644 --- a/submodules/TelegramUI/Sources/SpotlightContacts.swift +++ b/submodules/TelegramUI/Sources/SpotlightContacts.swift @@ -224,12 +224,12 @@ private func manageableSpotlightContacts(appBasePath: String, accounts: Signal<[ private final class SpotlightDataContextImpl { private let queue: Queue private let appBasePath: String - private let accountManager: AccountManager + private let accountManager: AccountManager private let indexStorage: SpotlightIndexStorage private var listDisposable: Disposable? - init(queue: Queue, appBasePath: String, accountManager: AccountManager, accounts: Signal<[Account], NoError>) { + init(queue: Queue, appBasePath: String, accountManager: AccountManager, accounts: Signal<[Account], NoError>) { self.queue = queue self.appBasePath = appBasePath self.accountManager = accountManager @@ -266,7 +266,7 @@ private final class SpotlightDataContextImpl { public final class SpotlightDataContext { private let impl: QueueLocalObject - public init(appBasePath: String, accountManager: AccountManager, accounts: Signal<[Account], NoError>) { + public init(appBasePath: String, accountManager: AccountManager, accounts: Signal<[Account], NoError>) { let queue = Queue() self.impl = QueueLocalObject(queue: queue, generate: { return SpotlightDataContextImpl(queue: queue, appBasePath: appBasePath, accountManager: accountManager, accounts: accounts) diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift b/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift index e079962089..ef4f2659e8 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift +++ b/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift @@ -78,6 +78,7 @@ final class StickerPaneSearchGlobalItem: GridItem { let theme: PresentationTheme let strings: PresentationStrings let listAppearance: Bool + let fillsRow: Bool let info: StickerPackCollectionInfo let topItems: [StickerPackItem] let topSeparator: Bool @@ -102,14 +103,15 @@ final class StickerPaneSearchGlobalItem: GridItem { } } - return (128.0 + additionalHeight, !self.listAppearance) + return (128.0 + additionalHeight, self.fillsRow) } - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) { + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, fillsRow: Bool = true, info: StickerPackCollectionInfo, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) { self.account = account self.theme = theme self.strings = strings self.listAppearance = listAppearance + self.fillsRow = fillsRow self.info = info self.topItems = topItems self.topSeparator = topSeparator @@ -155,8 +157,9 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { private let uninstallButtonNode: HighlightTrackingButtonNode private var itemNodes: [TrendingTopItemNode] private let topSeparatorNode: ASDisplayNode + private var highlightNode: ASDisplayNode? - private var item: StickerPaneSearchGlobalItem? + var item: StickerPaneSearchGlobalItem? private var appliedItem: StickerPaneSearchGlobalItem? private let preloadDisposable = MetaDisposable() private let preloadedStickerPackThumbnailDisposable = MetaDisposable() @@ -330,6 +333,27 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { self.canPlayMedia = item.itemContext.canPlayMedia } + func highlight() { + guard self.highlightNode == nil else { + return + } + + let highlightNode = ASDisplayNode() + highlightNode.frame = self.bounds + if let theme = self.item?.theme { + highlightNode.backgroundColor = theme.list.itemCheckColors.fillColor.withAlphaComponent(0.08) + } + self.highlightNode = highlightNode + self.insertSubnode(highlightNode, at: 0) + + Queue.mainQueue().after(1.5) { + self.highlightNode = nil + highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak highlightNode] _ in + highlightNode?.removeFromSupernode() + }) + } + } + override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) { guard let item = self.item else { return diff --git a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift new file mode 100644 index 0000000000..72522260f3 --- /dev/null +++ b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift @@ -0,0 +1,509 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import TelegramCore +import SwiftSignalKit +import Postbox +import TelegramPresentationData +import StickerResources +import ItemListStickerPackItem +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import ShimmerEffect +import MergeLists + +private let boundingSize = CGSize(width: 41.0, height: 41.0) +private let boundingImageSize = CGSize(width: 28.0, height: 28.0) + +private struct Transition { + let deletions: [ListViewDeleteItem] + let insertions: [ListViewInsertItem] + let updates: [ListViewUpdateItem] +} + +private enum EntryStableId: Hashable { + case stickerPack(Int64) +} + +private enum Entry: Comparable, Identifiable { + case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?, unread: Bool, theme: PresentationTheme) + + var stableId: EntryStableId { + switch self { + case let .stickerPack(_, info, _, _, _): + return .stickerPack(info.id.id) + } + } + + static func ==(lhs: Entry, rhs: Entry) -> Bool { + switch lhs { + case let .stickerPack(index, info, topItem, lhsUnread, lhsTheme): + if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem, rhsUnread, rhsTheme) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem, lhsUnread == rhsUnread, lhsTheme === rhsTheme { + return true + } else { + return false + } + } + } + + static func <(lhs: Entry, rhs: Entry) -> Bool { + switch lhs { + case let .stickerPack(lhsIndex, lhsInfo, _, _, _): + switch rhs { + case let .stickerPack(rhsIndex, rhsInfo, _, _, _): + if lhsIndex == rhsIndex { + return lhsInfo.id.id < rhsInfo.id.id + } else { + return lhsIndex <= rhsIndex + } + } + } + } + + func item(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction) -> ListViewItem { + switch self { + case let .stickerPack(index, info, topItem, unread, theme): + return FeaturedPackItem(account: account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, collectionInfo: info, stickerPackItem: topItem, unread: unread, index: index, theme: theme, selected: { + inputNodeInteraction.openTrending(info.id) + }) + } + } +} + +private func preparedEntryTransition(account: Account, from fromEntries: [Entry], to toEntries: [Entry], inputNodeInteraction: ChatMediaInputNodeInteraction) -> Transition { + 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(account: account, inputNodeInteraction: inputNodeInteraction), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, inputNodeInteraction: inputNodeInteraction), directionHint: nil) } + + return Transition(deletions: deletions, insertions: insertions, updates: updates) +} + +private func panelEntries(featuredPacks: [FeaturedStickerPackItem], theme: PresentationTheme) -> [Entry] { + var entries: [Entry] = [] + var index = 0 + for pack in featuredPacks { + entries.append(.stickerPack(index: index, info: pack.info, topItem: pack.topItems.first, unread: pack.unread, theme: theme)) + index += 1 + } + return entries +} + +private final class FeaturedPackItem: ListViewItem { + let account: Account + let inputNodeInteraction: ChatMediaInputNodeInteraction + let collectionId: ItemCollectionId + let collectionInfo: StickerPackCollectionInfo + let stickerPackItem: StickerPackItem? + let unread: Bool + let selectedItem: () -> Void + let index: Int + let theme: PresentationTheme + + var selectable: Bool { + return true + } + + init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo, stickerPackItem: StickerPackItem?, unread: Bool, index: Int, theme: PresentationTheme, selected: @escaping () -> Void) { + self.account = account + self.inputNodeInteraction = inputNodeInteraction + self.collectionId = collectionId + self.collectionInfo = collectionInfo + self.stickerPackItem = stickerPackItem + self.unread = unread + self.index = index + self.theme = theme + self.selectedItem = selected + } + + 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 = FeaturedPackItemNode() + node.contentSize = boundingSize + node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem) + node.inputNodeInteraction = self.inputNodeInteraction + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in + node.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, unread: self.unread, theme: self.theme) + }) + }) + } + } + } + + 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 { + completion(ListViewItemNodeLayout(contentSize: boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in + (node() as? FeaturedPackItemNode)?.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, unread: self.unread, theme: self.theme) + }) + } + } + + func selected(listView: ListView) { + self.selectedItem() + } +} + +private final class FeaturedPackItemNode: ListViewItemNode { + private let containerNode: ASDisplayNode + private let imageNode: TransformImageNode + private var animatedStickerNode: AnimatedStickerNode? + private var placeholderNode: StickerShimmerEffectNode? + private let unreadNode: ASImageNode + + var inputNodeInteraction: ChatMediaInputNodeInteraction? + var currentCollectionId: ItemCollectionId? + private var currentThumbnailItem: StickerPackThumbnailItem? + private var theme: PresentationTheme? + + private let stickerFetchedDisposable = MetaDisposable() + + override var visibility: ListViewItemNodeVisibility { + didSet { + self.visibilityStatus = self.visibility != .none + } + } + + var visibilityStatus: Bool = false { + didSet { + if self.visibilityStatus != oldValue { + let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false + self.animatedStickerNode?.visibility = self.visibilityStatus && loopAnimatedStickers + } + } + } + + init() { + self.containerNode = ASDisplayNode() + self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + + self.imageNode = TransformImageNode() + self.imageNode.isLayerBacked = !smartInvertColorsEnabled() + + self.placeholderNode = StickerShimmerEffectNode() + + self.unreadNode = ASImageNode() + self.unreadNode.isLayerBacked = true + self.unreadNode.displayWithoutProcessing = true + self.unreadNode.displaysAsynchronously = false + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.containerNode) + + self.containerNode.addSubnode(self.imageNode) + if let placeholderNode = self.placeholderNode { + self.containerNode.addSubnode(placeholderNode) + } + self.containerNode.addSubnode(self.unreadNode) + + var firstTime = true + self.imageNode.imageUpdated = { [weak self] image in + guard let strongSelf = self else { + return + } + if image != nil { + strongSelf.removePlaceholder(animated: !firstTime) + if firstTime { + strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + firstTime = false + } + } + + deinit { + self.stickerFetchedDisposable.dispose() + } + + private func removePlaceholder(animated: Bool) { + if let placeholderNode = self.placeholderNode { + self.placeholderNode = nil + if !animated { + placeholderNode.removeFromSupernode() + } else { + placeholderNode.alpha = 0.0 + placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in + placeholderNode?.removeFromSupernode() + }) + } + } + } + + func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, unread: Bool, theme: PresentationTheme) { + self.currentCollectionId = collectionId + + if self.theme !== theme { + self.theme = theme + } + + var thumbnailItem: StickerPackThumbnailItem? + var resourceReference: MediaResourceReference? + if let thumbnail = info.thumbnail { + if info.flags.contains(.isAnimated) { + thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions) + resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource) + } else { + thumbnailItem = .still(thumbnail) + resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource) + } + } else if let item = item { + if item.file.isAnimatedSticker { + thumbnailItem = .animated(item.file.resource, item.file.dimensions ?? PixelDimensions(width: 100, height: 100)) + resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource) + } else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { + thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil)) + resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource) + } + } + + var imageSize = boundingImageSize + + if self.currentThumbnailItem != thumbnailItem { + self.currentThumbnailItem = thumbnailItem + let thumbnailDimensions = PixelDimensions(width: 512, height: 512) + if let thumbnailItem = thumbnailItem { + switch thumbnailItem { + case let .still(representation): + imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize) + let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) + imageApply() + self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true)) + case let .animated(resource, _): + let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) + imageApply() + 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() + self.animatedStickerNode = animatedStickerNode + if let placeholderNode = self.placeholderNode { + self.containerNode.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode) + } else { + self.containerNode.addSubnode(animatedStickerNode) + } + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 128, height: 128, mode: .cached) + } + animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers + } + if let resourceReference = resourceReference { + self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference).start()) + } + } + + if let placeholderNode = self.placeholderNode { + let imageSize = boundingImageSize + placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0), foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.15), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), data: info.immediateThumbnailData, size: imageSize, imageSize: thumbnailDimensions.cgSize) + } + } + + self.containerNode.frame = CGRect(origin: CGPoint(), size: boundingSize) + + self.imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize) + self.imageNode.position = CGPoint(x: boundingSize.height / 2.0, y: boundingSize.width / 2.0) + if let animatedStickerNode = self.animatedStickerNode { + animatedStickerNode.frame = self.imageNode.frame + animatedStickerNode.updateLayout(size: self.imageNode.frame.size) + } + if let placeholderNode = self.placeholderNode { + placeholderNode.bounds = CGRect(origin: CGPoint(), size: boundingImageSize) + placeholderNode.position = self.imageNode.position + } + + let unreadImage = PresentationResourcesItemList.stickerUnreadDotImage(theme) + if unread { + self.unreadNode.isHidden = false + } else { + self.unreadNode.isHidden = true + } + if let image = unreadImage { + self.unreadNode.image = image + self.unreadNode.frame = CGRect(origin: CGPoint(x: 35.0, y: 4.0), size: image.size) + } + } + + override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + if let placeholderNode = self.placeholderNode { + placeholderNode.updateAbsoluteRect(rect, within: containerSize) + } + } + + override func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } +} + + +final class StickerPaneTrendingListGridItem: GridItem { + let account: Account + let theme: PresentationTheme + let strings: PresentationStrings + let trendingPacks: [FeaturedStickerPackItem] + let inputNodeInteraction: ChatMediaInputNodeInteraction + let dismiss: (() -> Void)? + + let section: GridSection? = nil + let fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? + + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, trendingPacks: [FeaturedStickerPackItem], inputNodeInteraction: ChatMediaInputNodeInteraction, dismiss: (() -> Void)?) { + self.account = account + self.theme = theme + self.strings = strings + self.trendingPacks = trendingPacks + self.inputNodeInteraction = inputNodeInteraction + self.dismiss = dismiss + self.fillsRowWithDynamicHeight = { _ in + return 70.0 + } + } + + func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { + let node = StickerPaneTrendingListGridItemNode() + node.setup(item: self) + return node + } + + func update(node: GridItemNode) { + guard let node = node as? StickerPaneTrendingListGridItemNode else { + assertionFailure() + return + } + node.setup(item: self) + } +} + +private let titleFont = Font.medium(12.0) + +class StickerPaneTrendingListGridItemNode: GridItemNode { + private let titleNode: TextNode + private let dismissButtonNode: HighlightTrackingButtonNode + + private let listView: ListView + + private var item: StickerPaneTrendingListGridItem? + private var appliedItem: StickerPaneTrendingListGridItem? + + override var isVisibleInGrid: Bool { + didSet { + self.updateVisibility() + } + } + + private let disposable = MetaDisposable() + private var currentEntries: [Entry] = [] + + override init() { + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + self.dismissButtonNode = HighlightTrackingButtonNode() + + self.listView = ListView() + self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) + self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.listView) + self.addSubnode(self.dismissButtonNode) + + self.dismissButtonNode.addTarget(self, action: #selector(self.dismissPressed), forControlEvents: .touchUpInside) + } + + deinit { + self.disposable.dispose() + } + + private func enqueuePanelTransition(_ transition: Transition, firstTime: Bool) { + var options = ListViewDeleteAndInsertOptions() + if firstTime { + options.insert(.Synchronous) + options.insert(.LowLatency) + } else { + options.insert(.AnimateInsertion) + } + + self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateOpaqueState: nil, completion: { _ in }) + } + + func setup(item: StickerPaneTrendingListGridItem) { + self.item = item + + let entries = panelEntries(featuredPacks: item.trendingPacks, theme: item.theme) + let transition = preparedEntryTransition(account: item.account, from: self.currentEntries, to: entries, inputNodeInteraction: item.inputNodeInteraction) + self.enqueuePanelTransition(transition, firstTime: self.currentEntries.isEmpty) + self.currentEntries = entries + + self.setNeedsLayout() + } + + func updateVisibility() { + + } + + override func layout() { + super.layout() + guard let item = self.item else { + return + } + + let params = ListViewItemLayoutParams(width: self.bounds.size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: self.bounds.size.height) + + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + + let currentItem = self.appliedItem + self.appliedItem = item + + let width = self.bounds.size.width + + self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0, height: width) + self.listView.position = CGPoint(x: width / 2.0, y: 26.0 + 41.0 / 2.0) + + let (duration, curve) = listViewAnimationDurationAndCurve(transition: .immediate) + let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0, height: self.bounds.size.width), insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), duration: duration, curve: curve) + + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + + if currentItem?.theme !== item.theme { + self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme), for: []) + } + + let leftInset: CGFloat = 12.0 + let rightInset: CGFloat = 16.0 + let topOffset: CGFloat = 9.0 + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.StickerPacksSettings_FeaturedPacks.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + self.item = item + + let _ = titleApply() + + let titleFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset), size: titleLayout.size) + let dismissButtonSize = CGSize(width: 12.0, height: 12.0) + self.dismissButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - dismissButtonSize.width, y: topOffset - 1.0), size: dismissButtonSize) + self.dismissButtonNode.isHidden = item.dismiss == nil + self.titleNode.frame = titleFrame + } + + @objc private func dismissPressed() { + if let item = self.item { + item.dismiss?() + } + } +} diff --git a/submodules/TelegramUI/Sources/StoreDownloadedMedia.swift b/submodules/TelegramUI/Sources/StoreDownloadedMedia.swift index cde6ba0158..0023d3a103 100644 --- a/submodules/TelegramUI/Sources/StoreDownloadedMedia.swift +++ b/submodules/TelegramUI/Sources/StoreDownloadedMedia.swift @@ -149,7 +149,7 @@ private final class DownloadedMediaStoreManagerPrivateImpl { private let appSpecificAssetCollectionValue: Promise private let storeSettings = Promise() - init(queue: Queue, postbox: Postbox, accountManager: AccountManager) { + init(queue: Queue, postbox: Postbox, accountManager: AccountManager) { self.queue = queue self.postbox = postbox @@ -199,7 +199,7 @@ final class DownloadedMediaStoreManagerImpl: DownloadedMediaStoreManager { private let queue = Queue() private let impl: QueueLocalObject - init(postbox: Postbox, accountManager: AccountManager) { + init(postbox: Postbox, accountManager: AccountManager) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { return DownloadedMediaStoreManagerPrivateImpl(queue: queue, postbox: postbox, accountManager: accountManager) diff --git a/submodules/TelegramUI/Sources/StringForMessageTimestampStatus.swift b/submodules/TelegramUI/Sources/StringForMessageTimestampStatus.swift index 403338e8c7..dde2995516 100644 --- a/submodules/TelegramUI/Sources/StringForMessageTimestampStatus.swift +++ b/submodules/TelegramUI/Sources/StringForMessageTimestampStatus.swift @@ -29,6 +29,11 @@ private func dateStringForDay(strings: PresentationStrings, dateTimeFormat: Pres } func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular, reactionCount: Int) -> String { + if message.adAttribute != nil { + //TODO:localize + return "sponsored" + } + let timestamp: Int32 if let scheduleTime = message.scheduleTime { timestamp = scheduleTime 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/TelegramUI/Sources/UpgradedAccounts.swift b/submodules/TelegramUI/Sources/UpgradedAccounts.swift index fc08028067..a02625e868 100644 --- a/submodules/TelegramUI/Sources/UpgradedAccounts.swift +++ b/submodules/TelegramUI/Sources/UpgradedAccounts.swift @@ -110,7 +110,7 @@ private func upgradedSharedDataValue(_ value: PreferencesEntry?) -> PreferencesE } } -public func upgradedAccounts(accountManager: AccountManager, rootPath: String, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { +public func upgradedAccounts(accountManager: AccountManager, rootPath: String, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { return accountManager.transaction { transaction -> (Int32?, AccountRecordId?) in return (transaction.getVersion(), transaction.getCurrent()?.0) } @@ -237,7 +237,7 @@ public func upgradedAccounts(accountManager: AccountManager, rootPath: String, e var index: Int32 = 0 for record in transaction.getRecords() { transaction.updateRecord(record.id, { _ in - return AccountRecord(id: record.id, attributes: record.attributes + [AccountSortOrderAttribute(order: index)], temporarySessionId: record.temporarySessionId) + return AccountRecord(id: record.id, attributes: record.attributes + [.sortOrder(AccountSortOrderAttribute(order: index))], temporarySessionId: record.temporarySessionId) }) index += 1 } diff --git a/submodules/TelegramUIPreferences/Sources/CallListSettings.swift b/submodules/TelegramUIPreferences/Sources/CallListSettings.swift index e40d334a9b..55383bcdbc 100644 --- a/submodules/TelegramUIPreferences/Sources/CallListSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/CallListSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct CallListSettings: PreferencesEntry, Equatable { @@ -72,7 +73,7 @@ public struct CallListSettings: PreferencesEntry, Equatable { } } -public func updateCallListSettingsInteractively(accountManager: AccountManager, _ f: @escaping (CallListSettings) -> CallListSettings) -> Signal { +public func updateCallListSettingsInteractively(accountManager: AccountManager, _ f: @escaping (CallListSettings) -> CallListSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.callListSettings, { entry in let currentSettings: CallListSettings diff --git a/submodules/TelegramUIPreferences/Sources/ContactSynchronizationSettings.swift b/submodules/TelegramUIPreferences/Sources/ContactSynchronizationSettings.swift index 98ae147d31..f80a3851a4 100644 --- a/submodules/TelegramUIPreferences/Sources/ContactSynchronizationSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ContactSynchronizationSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public enum ContactsSortOrder: Int32 { @@ -48,7 +49,7 @@ public struct ContactSynchronizationSettings: Equatable, PreferencesEntry { } } -public func updateContactSettingsInteractively(accountManager: AccountManager, _ f: @escaping (ContactSynchronizationSettings) -> ContactSynchronizationSettings) -> Signal { +public func updateContactSettingsInteractively(accountManager: AccountManager, _ f: @escaping (ContactSynchronizationSettings) -> ContactSynchronizationSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.contactSynchronizationSettings, { entry in let currentSettings: ContactSynchronizationSettings diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalSettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalSettings.swift index 53c722b3f2..ace0e5f197 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct ExperimentalSettings: PreferencesEntry, Equatable { @@ -34,7 +35,7 @@ public struct ExperimentalSettings: PreferencesEntry, Equatable { } } -public func updateExperimentalSettingsInteractively(accountManager: AccountManager, _ f: @escaping (ExperimentalSettings) -> ExperimentalSettings) -> Signal { +public func updateExperimentalSettingsInteractively(accountManager: AccountManager, _ f: @escaping (ExperimentalSettings) -> ExperimentalSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalSettings, { entry in let currentSettings: ExperimentalSettings diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index d08fd83283..bece6cae74 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct ExperimentalUISettings: Equatable, PreferencesEntry { @@ -114,7 +115,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { } } -public func updateExperimentalUISettingsInteractively(accountManager: AccountManager, _ f: @escaping (ExperimentalUISettings) -> ExperimentalUISettings) -> Signal { +public func updateExperimentalUISettingsInteractively(accountManager: AccountManager, _ f: @escaping (ExperimentalUISettings) -> ExperimentalUISettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { entry in let currentSettings: ExperimentalUISettings diff --git a/submodules/TelegramUIPreferences/Sources/GeneratedMediaStoreSettings.swift b/submodules/TelegramUIPreferences/Sources/GeneratedMediaStoreSettings.swift index 3b711ef296..c8473ee338 100644 --- a/submodules/TelegramUIPreferences/Sources/GeneratedMediaStoreSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/GeneratedMediaStoreSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct GeneratedMediaStoreSettings: PreferencesEntry, Equatable { @@ -42,7 +43,7 @@ public struct GeneratedMediaStoreSettings: PreferencesEntry, Equatable { } } -public func updateGeneratedMediaStoreSettingsInteractively(accountManager: AccountManager, _ f: @escaping (GeneratedMediaStoreSettings) -> GeneratedMediaStoreSettings) -> Signal { +public func updateGeneratedMediaStoreSettingsInteractively(accountManager: AccountManager, _ f: @escaping (GeneratedMediaStoreSettings) -> GeneratedMediaStoreSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings, { entry in let currentSettings: GeneratedMediaStoreSettings diff --git a/submodules/TelegramUIPreferences/Sources/InAppNotificationSettings.swift b/submodules/TelegramUIPreferences/Sources/InAppNotificationSettings.swift index 6cb40aac97..458f92472e 100644 --- a/submodules/TelegramUIPreferences/Sources/InAppNotificationSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/InAppNotificationSettings.swift @@ -103,7 +103,7 @@ public struct InAppNotificationSettings: PreferencesEntry, Equatable { } } -public func updateInAppNotificationSettingsInteractively(accountManager: AccountManager, _ f: @escaping (InAppNotificationSettings) -> InAppNotificationSettings) -> Signal { +public func updateInAppNotificationSettingsInteractively(accountManager: AccountManager, _ f: @escaping (InAppNotificationSettings) -> InAppNotificationSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.inAppNotificationSettings, { entry in let currentSettings: InAppNotificationSettings diff --git a/submodules/TelegramUIPreferences/Sources/InstantPagePresentationSettings.swift b/submodules/TelegramUIPreferences/Sources/InstantPagePresentationSettings.swift index b2b696215a..24196f6a02 100644 --- a/submodules/TelegramUIPreferences/Sources/InstantPagePresentationSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/InstantPagePresentationSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public enum InstantPageThemeType: Int32 { @@ -98,7 +99,7 @@ public final class InstantPagePresentationSettings: PreferencesEntry, Equatable } } -public func updateInstantPagePresentationSettingsInteractively(accountManager: AccountManager, _ f: @escaping (InstantPagePresentationSettings) -> InstantPagePresentationSettings) -> Signal { +public func updateInstantPagePresentationSettingsInteractively(accountManager: AccountManager, _ f: @escaping (InstantPagePresentationSettings) -> InstantPagePresentationSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.instantPagePresentationSettings, { entry in let currentSettings: InstantPagePresentationSettings diff --git a/submodules/TelegramUIPreferences/Sources/IntentsSettings.swift b/submodules/TelegramUIPreferences/Sources/IntentsSettings.swift index 77ba26eea7..221f946fe0 100644 --- a/submodules/TelegramUIPreferences/Sources/IntentsSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/IntentsSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct IntentsSettings: PreferencesEntry, Equatable { @@ -88,7 +89,7 @@ public struct IntentsSettings: PreferencesEntry, Equatable { } -public func updateIntentsSettingsInteractively(accountManager: AccountManager, _ f: @escaping (IntentsSettings) -> IntentsSettings) -> Signal<(IntentsSettings?, IntentsSettings?), NoError> { +public func updateIntentsSettingsInteractively(accountManager: AccountManager, _ f: @escaping (IntentsSettings) -> IntentsSettings) -> Signal<(IntentsSettings?, IntentsSettings?), NoError> { return accountManager.transaction { transaction -> (IntentsSettings?, IntentsSettings?) in var previousSettings: IntentsSettings? = nil var updatedSettings: IntentsSettings? = nil diff --git a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift index 2e5d132c07..c6a262d0a7 100644 --- a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift @@ -257,7 +257,7 @@ private func presetsWithAutodownloadSettings(_ autodownloadSettings: Autodownloa return MediaAutoDownloadPresets(low: categoriesWithAutodownloadPreset(autodownloadSettings.lowPreset, preset: .low), medium: categoriesWithAutodownloadPreset(autodownloadSettings.mediumPreset, preset: .medium), high: categoriesWithAutodownloadPreset(autodownloadSettings.highPreset, preset: .high)) } -public func updateMediaDownloadSettingsInteractively(accountManager: AccountManager, _ f: @escaping (MediaAutoDownloadSettings) -> MediaAutoDownloadSettings) -> Signal { +public func updateMediaDownloadSettingsInteractively(accountManager: AccountManager, _ f: @escaping (MediaAutoDownloadSettings) -> MediaAutoDownloadSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, { entry in let currentSettings: MediaAutoDownloadSettings diff --git a/submodules/TelegramUIPreferences/Sources/MediaInputSettings.swift b/submodules/TelegramUIPreferences/Sources/MediaInputSettings.swift index db6fba2ae2..e1c5f49d42 100644 --- a/submodules/TelegramUIPreferences/Sources/MediaInputSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MediaInputSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct MediaInputSettings: PreferencesEntry, Equatable { @@ -38,7 +39,7 @@ public struct MediaInputSettings: PreferencesEntry, Equatable { } } -public func updateMediaInputSettingsInteractively(accountManager: AccountManager, _ f: @escaping (MediaInputSettings) -> MediaInputSettings) -> Signal { +public func updateMediaInputSettingsInteractively(accountManager: AccountManager, _ f: @escaping (MediaInputSettings) -> MediaInputSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.mediaInputSettings, { entry in let currentSettings: MediaInputSettings diff --git a/submodules/TelegramUIPreferences/Sources/MusicPlaybackSettings.swift b/submodules/TelegramUIPreferences/Sources/MusicPlaybackSettings.swift index d2707fa138..37d28f873f 100644 --- a/submodules/TelegramUIPreferences/Sources/MusicPlaybackSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MusicPlaybackSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public enum MusicPlaybackSettingsOrder: Int32 { @@ -88,7 +89,7 @@ public struct MusicPlaybackSettings: PreferencesEntry, Equatable { } } -public func updateMusicPlaybackSettingsInteractively(accountManager: AccountManager, _ f: @escaping (MusicPlaybackSettings) -> MusicPlaybackSettings) -> Signal { +public func updateMusicPlaybackSettingsInteractively(accountManager: AccountManager, _ f: @escaping (MusicPlaybackSettings) -> MusicPlaybackSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { entry in let currentSettings: MusicPlaybackSettings diff --git a/submodules/TelegramUIPreferences/Sources/PresentationPasscodeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationPasscodeSettings.swift index 6d212f46a2..45c1b0dcec 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationPasscodeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationPasscodeSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct PresentationPasscodeSettings: PreferencesEntry, Equatable { @@ -74,13 +75,13 @@ public struct PresentationPasscodeSettings: PreferencesEntry, Equatable { } } -public func updatePresentationPasscodeSettingsInteractively(accountManager: AccountManager, _ f: @escaping (PresentationPasscodeSettings) -> PresentationPasscodeSettings) -> Signal { +public func updatePresentationPasscodeSettingsInteractively(accountManager: AccountManager, _ f: @escaping (PresentationPasscodeSettings) -> PresentationPasscodeSettings) -> Signal { return accountManager.transaction { transaction -> Void in updatePresentationPasscodeSettingsInternal(transaction: transaction, f) } } -public func updatePresentationPasscodeSettingsInternal(transaction: AccountManagerModifier, _ f: @escaping (PresentationPasscodeSettings) -> PresentationPasscodeSettings) { +public func updatePresentationPasscodeSettingsInternal(transaction: AccountManagerModifier, _ f: @escaping (PresentationPasscodeSettings) -> PresentationPasscodeSettings) { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationPasscodeSettings, { entry in let currentSettings: PresentationPasscodeSettings if let entry = entry as? PresentationPasscodeSettings { diff --git a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift index 13521e4c20..fa7a55fe50 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift @@ -407,13 +407,13 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable { public struct PresentationThemeAccentColor: PostboxCoding, Equatable { public static func == (lhs: PresentationThemeAccentColor, rhs: PresentationThemeAccentColor) -> Bool { - return lhs.index == rhs.index && lhs.baseColor == rhs.baseColor && lhs.accentColor == rhs.accentColor && lhs.bubbleColors?.0 == rhs.bubbleColors?.0 && lhs.bubbleColors?.1 == rhs.bubbleColors?.1 + return lhs.index == rhs.index && lhs.baseColor == rhs.baseColor && lhs.accentColor == rhs.accentColor && lhs.bubbleColors == rhs.bubbleColors } public var index: Int32 public var baseColor: PresentationThemeBaseColor public var accentColor: UInt32? - public var bubbleColors: (UInt32, UInt32?)? + public var bubbleColors: [UInt32] public var wallpaper: TelegramWallpaper? public var themeIndex: Int64? @@ -425,11 +425,11 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { } self.baseColor = baseColor self.accentColor = nil - self.bubbleColors = nil + self.bubbleColors = [] self.wallpaper = nil } - public init(index: Int32, baseColor: PresentationThemeBaseColor, accentColor: UInt32? = nil, bubbleColors: (UInt32, UInt32?)? = nil, wallpaper: TelegramWallpaper? = nil) { + public init(index: Int32, baseColor: PresentationThemeBaseColor, accentColor: UInt32? = nil, bubbleColors: [UInt32] = [], wallpaper: TelegramWallpaper? = nil) { self.index = index self.baseColor = baseColor self.accentColor = accentColor @@ -441,7 +441,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { self.index = -1 self.baseColor = .theme self.accentColor = nil - self.bubbleColors = nil + self.bubbleColors = [] self.wallpaper = nil self.themeIndex = themeIndex } @@ -450,15 +450,22 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { self.index = decoder.decodeInt32ForKey("i", orElse: -1) self.baseColor = PresentationThemeBaseColor(rawValue: decoder.decodeInt32ForKey("b", orElse: 0)) ?? .blue self.accentColor = decoder.decodeOptionalInt32ForKey("c").flatMap { UInt32(bitPattern: $0) } - if let bubbleTopColor = decoder.decodeOptionalInt32ForKey("bt") { - if let bubbleBottomColor = decoder.decodeOptionalInt32ForKey("bb") { - self.bubbleColors = (UInt32(bitPattern: bubbleTopColor), UInt32(bitPattern: bubbleBottomColor)) - } else { - self.bubbleColors = (UInt32(bitPattern: bubbleTopColor), nil) - } + + let bubbleColors = decoder.decodeInt32ArrayForKey("bubbleColors") + if !bubbleColors.isEmpty { + self.bubbleColors = bubbleColors.map(UInt32.init(bitPattern:)) } else { - self.bubbleColors = nil + if let bubbleTopColor = decoder.decodeOptionalInt32ForKey("bt") { + if let bubbleBottomColor = decoder.decodeOptionalInt32ForKey("bb") { + self.bubbleColors = [UInt32(bitPattern: bubbleTopColor), UInt32(bitPattern: bubbleBottomColor)] + } else { + self.bubbleColors = [UInt32(bitPattern: bubbleTopColor)] + } + } else { + self.bubbleColors = [] + } } + self.wallpaper = decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper self.themeIndex = decoder.decodeOptionalInt64ForKey("t") } @@ -471,17 +478,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "c") } - if let bubbleColors = self.bubbleColors { - encoder.encodeInt32(Int32(bitPattern: bubbleColors.0), forKey: "bt") - if let bubbleBottomColor = bubbleColors.1 { - encoder.encodeInt32(Int32(bitPattern: bubbleBottomColor), forKey: "bb") - } else { - encoder.encodeNil(forKey: "bb") - } - } else { - encoder.encodeNil(forKey: "bt") - encoder.encodeNil(forKey: "bb") - } + encoder.encodeInt32Array(self.bubbleColors.map(Int32.init(bitPattern:)), forKey: "bubbleColors") if let wallpaper = self.wallpaper { encoder.encodeObject(wallpaper, forKey: "w") } else { @@ -502,28 +499,12 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { } } - public var customBubbleColors: (UIColor, UIColor?)? { - if let bubbleColors = self.bubbleColors { - if let bottomColor = bubbleColors.1 { - return (UIColor(rgb: UInt32(bitPattern: bubbleColors.0)), UIColor(rgb: UInt32(bitPattern: bottomColor))) - } else { - return (UIColor(rgb: UInt32(bitPattern: bubbleColors.0)), nil) - } - } else { - return nil - } + public var customBubbleColors: [UInt32] { + return self.bubbleColors } - public var plainBubbleColors: (UIColor, UIColor)? { - if let bubbleColors = self.bubbleColors { - if let bottomColor = bubbleColors.1 { - return (UIColor(rgb: UInt32(bitPattern: bubbleColors.0)), UIColor(rgb: UInt32(bitPattern: bottomColor))) - } else { - return (UIColor(rgb: UInt32(bitPattern: bubbleColors.0)), UIColor(rgb: UInt32(bitPattern: bubbleColors.0))) - } - } else { - return nil - } + public var plainBubbleColors: [UInt32] { + return self.bubbleColors } public func withUpdatedWallpaper(_ wallpaper: TelegramWallpaper?) -> PresentationThemeAccentColor { @@ -712,7 +693,7 @@ public struct PresentationThemeSettings: PreferencesEntry { } } -public func updatePresentationThemeSettingsInteractively(accountManager: AccountManager, _ f: @escaping (PresentationThemeSettings) -> PresentationThemeSettings) -> Signal { +public func updatePresentationThemeSettingsInteractively(accountManager: AccountManager, _ f: @escaping (PresentationThemeSettings) -> PresentationThemeSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { entry in let currentSettings: PresentationThemeSettings diff --git a/submodules/TelegramUIPreferences/Sources/RenderedTotalUnreadCount.swift b/submodules/TelegramUIPreferences/Sources/RenderedTotalUnreadCount.swift index 2e2fab0bd1..dff99b50bf 100644 --- a/submodules/TelegramUIPreferences/Sources/RenderedTotalUnreadCount.swift +++ b/submodules/TelegramUIPreferences/Sources/RenderedTotalUnreadCount.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public enum RenderedTotalUnreadCountType { @@ -21,7 +22,7 @@ public func renderedTotalUnreadCount(inAppSettings: InAppNotificationSettings, t return (totalUnreadState.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags), type) } -public func renderedTotalUnreadCount(accountManager: AccountManager, postbox: Postbox) -> Signal<(Int32, RenderedTotalUnreadCountType), NoError> { +public func renderedTotalUnreadCount(accountManager: AccountManager, postbox: Postbox) -> Signal<(Int32, RenderedTotalUnreadCountType), NoError> { let unreadCountsKey = PostboxViewKey.unreadCounts(items: [.total(nil)]) return combineLatest(accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]), postbox.combinedView(keys: [unreadCountsKey])) |> map { sharedData, view -> (Int32, RenderedTotalUnreadCountType) in diff --git a/submodules/TelegramUIPreferences/Sources/StickerSettings.swift b/submodules/TelegramUIPreferences/Sources/StickerSettings.swift index 4eea5be1da..1f0adf1b4c 100644 --- a/submodules/TelegramUIPreferences/Sources/StickerSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/StickerSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public enum EmojiStickerSuggestionMode: Int32 { @@ -52,7 +53,7 @@ public struct StickerSettings: PreferencesEntry, Equatable { } } -public func updateStickerSettingsInteractively(accountManager: AccountManager, _ f: @escaping (StickerSettings) -> StickerSettings) -> Signal { +public func updateStickerSettingsInteractively(accountManager: AccountManager, _ f: @escaping (StickerSettings) -> StickerSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.stickerSettings, { entry in let currentSettings: StickerSettings diff --git a/submodules/TelegramUIPreferences/Sources/VoiceCallSettings.swift b/submodules/TelegramUIPreferences/Sources/VoiceCallSettings.swift index 91f87db642..3dc6a49852 100644 --- a/submodules/TelegramUIPreferences/Sources/VoiceCallSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/VoiceCallSettings.swift @@ -71,7 +71,7 @@ public struct VoiceCallSettings: PreferencesEntry, Equatable { } } -public func updateVoiceCallSettingsSettingsInteractively(accountManager: AccountManager, _ f: @escaping (VoiceCallSettings) -> VoiceCallSettings) -> Signal { +public func updateVoiceCallSettingsSettingsInteractively(accountManager: AccountManager, _ f: @escaping (VoiceCallSettings) -> VoiceCallSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.voiceCallSettings, { entry in let currentSettings: VoiceCallSettings diff --git a/submodules/TelegramUIPreferences/Sources/WatchPresetSettings.swift b/submodules/TelegramUIPreferences/Sources/WatchPresetSettings.swift index a6d09d5bca..c73fa77c0e 100644 --- a/submodules/TelegramUIPreferences/Sources/WatchPresetSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/WatchPresetSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct WatchPresetSettings: PreferencesEntry, Equatable { @@ -53,7 +54,7 @@ public struct WatchPresetSettings: PreferencesEntry, Equatable { } } -public func updateWatchPresetSettingsInteractively(accountManager: AccountManager, _ f: @escaping (WatchPresetSettings) -> WatchPresetSettings) -> Signal { +public func updateWatchPresetSettingsInteractively(accountManager: AccountManager, _ f: @escaping (WatchPresetSettings) -> WatchPresetSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.watchPresetSettings, { entry in let currentSettings: WatchPresetSettings diff --git a/submodules/TelegramUIPreferences/Sources/WebBrowserSettings.swift b/submodules/TelegramUIPreferences/Sources/WebBrowserSettings.swift index 09be868a4c..d2b0082fef 100644 --- a/submodules/TelegramUIPreferences/Sources/WebBrowserSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/WebBrowserSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public struct WebBrowserSettings: PreferencesEntry, Equatable { @@ -42,7 +43,7 @@ public struct WebBrowserSettings: PreferencesEntry, Equatable { } } -public func updateWebBrowserSettingsInteractively(accountManager: AccountManager, _ f: @escaping (WebBrowserSettings) -> WebBrowserSettings) -> Signal { +public func updateWebBrowserSettingsInteractively(accountManager: AccountManager, _ f: @escaping (WebBrowserSettings) -> WebBrowserSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.webBrowserSettings, { entry in let currentSettings: WebBrowserSettings diff --git a/submodules/TelegramUIPreferences/Sources/WebSearchSettings.swift b/submodules/TelegramUIPreferences/Sources/WebSearchSettings.swift index a21413a4d4..99c3c2b35e 100644 --- a/submodules/TelegramUIPreferences/Sources/WebSearchSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/WebSearchSettings.swift @@ -1,5 +1,6 @@ import Foundation import Postbox +import TelegramCore import SwiftSignalKit public enum WebSearchScope: Int32 { @@ -35,7 +36,7 @@ public struct WebSearchSettings: Equatable, PreferencesEntry { } } -public func updateWebSearchSettingsInteractively(accountManager: AccountManager, _ f: @escaping (WebSearchSettings) -> WebSearchSettings) -> Signal { +public func updateWebSearchSettingsInteractively(accountManager: AccountManager, _ f: @escaping (WebSearchSettings) -> WebSearchSettings) -> Signal { return accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.webSearchSettings, { entry in let currentSettings: WebSearchSettings diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 62e7fb1687..6cbf6afce5 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -28,8 +28,13 @@ private final class ContextQueueImpl: NSObject, OngoingCallThreadLocalContextQue } } +private enum BroadcastPartSubject { + case audio + case video(channelId: Int32, quality: OngoingGroupCallContext.VideoChannel.Quality) +} + private protocol BroadcastPartSource: AnyObject { - func requestPart(timestampMilliseconds: Int64, durationMilliseconds: Int64, completion: @escaping (OngoingGroupCallBroadcastPart) -> Void, rejoinNeeded: @escaping () -> Void) -> Disposable + func requestPart(timestampMilliseconds: Int64, durationMilliseconds: Int64, subject: BroadcastPartSubject, completion: @escaping (OngoingGroupCallBroadcastPart) -> Void, rejoinNeeded: @escaping () -> Void) -> Disposable } private final class NetworkBroadcastPartSource: BroadcastPartSource { @@ -46,7 +51,7 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource { self.accessHash = accessHash } - func requestPart(timestampMilliseconds: Int64, durationMilliseconds: Int64, completion: @escaping (OngoingGroupCallBroadcastPart) -> Void, rejoinNeeded: @escaping () -> Void) -> Disposable { + func requestPart(timestampMilliseconds: Int64, durationMilliseconds: Int64, subject: BroadcastPartSubject, completion: @escaping (OngoingGroupCallBroadcastPart) -> Void, rejoinNeeded: @escaping () -> Void) -> Disposable { let timestampIdMilliseconds: Int64 if timestampMilliseconds != 0 { timestampIdMilliseconds = timestampMilliseconds @@ -71,8 +76,23 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource { |> mapToSignal { [weak self] dataSource -> Signal in if let dataSource = dataSource { self?.dataSource = dataSource - return engine.calls.getAudioBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds) - |> map(Optional.init) + switch subject { + case .audio: + return engine.calls.getAudioBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds) + |> map(Optional.init) + case let .video(channelId, quality): + let mappedQuality: Int32 + switch quality { + case .thumbnail: + mappedQuality = 0 + case .medium: + mappedQuality = 1 + case .full: + mappedQuality = 2 + } + return engine.calls.getVideoBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds, channelId: channelId, quality: mappedQuality) + |> map(Optional.init) + } } else { return .single(nil) |> delay(2.0, queue: queue) @@ -403,17 +423,39 @@ public final class OngoingGroupCallContext { return OngoingGroupCallMediaChannelDescriptionTaskImpl(disposable: disposable) }, - requestBroadcastPart: { timestampMilliseconds, durationMilliseconds, completion in + requestAudioBroadcastPart: { timestampMilliseconds, durationMilliseconds, completion in let disposable = MetaDisposable() queue.async { - disposable.set(broadcastPartsSource?.requestPart(timestampMilliseconds: timestampMilliseconds, durationMilliseconds: durationMilliseconds, completion: completion, rejoinNeeded: { + disposable.set(broadcastPartsSource?.requestPart(timestampMilliseconds: timestampMilliseconds, durationMilliseconds: durationMilliseconds, subject: .audio, completion: completion, rejoinNeeded: { rejoinNeeded() })) } return OngoingGroupCallBroadcastPartTaskImpl(disposable: disposable) }, + requestVideoBroadcastPart: { timestampMilliseconds, durationMilliseconds, channelId, quality, completion in + let disposable = MetaDisposable() + + queue.async { + let mappedQuality: OngoingGroupCallContext.VideoChannel.Quality + switch quality { + case .thumbnail: + mappedQuality = .thumbnail + case .medium: + mappedQuality = .medium + case .full: + mappedQuality = .full + @unknown default: + mappedQuality = .thumbnail + } + disposable.set(broadcastPartsSource?.requestPart(timestampMilliseconds: timestampMilliseconds, durationMilliseconds: durationMilliseconds, subject: .video(channelId: channelId, quality: mappedQuality), completion: completion, rejoinNeeded: { + rejoinNeeded() + })) + } + + return OngoingGroupCallBroadcastPartTaskImpl(disposable: disposable) + }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit ?? 32, videoContentType: _videoContentType, enableNoiseSuppression: enableNoiseSuppression diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 55e3492af0..2ee2e8b817 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -344,7 +344,8 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) { outputDeviceId:(NSString * _Nonnull)outputDeviceId videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer requestMediaChannelDescriptions:(id _Nonnull (^ _Nonnull)(NSArray * _Nonnull, void (^ _Nonnull)(NSArray * _Nonnull)))requestMediaChannelDescriptions - requestBroadcastPart:(id _Nonnull (^ _Nonnull)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestBroadcastPart + requestAudioBroadcastPart:(id _Nonnull (^ _Nonnull)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestAudioBroadcastPart + requestVideoBroadcastPart:(id _Nonnull (^ _Nonnull)(int64_t, int64_t, int32_t, OngoingGroupCallRequestedVideoQuality, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestVideoBroadcastPart outgoingAudioBitrateKbit:(int32_t)outgoingAudioBitrateKbit videoContentType:(OngoingGroupCallVideoContentType)videoContentType enableNoiseSuppression:(bool)enableNoiseSuppression; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 9a652771a2..1f413bafe6 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -577,7 +577,7 @@ tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls: if (_keepLandscape) { resolvedId += std::string(":landscape"); } - _interface->switchToDevice(resolvedId); + _interface->switchToDevice(resolvedId, false); } - (void)setIsVideoEnabled:(bool)isVideoEnabled { @@ -1361,7 +1361,8 @@ private: outputDeviceId:(NSString * _Nonnull)outputDeviceId videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer requestMediaChannelDescriptions:(id _Nonnull (^ _Nonnull)(NSArray * _Nonnull, void (^ _Nonnull)(NSArray * _Nonnull)))requestMediaChannelDescriptions - requestBroadcastPart:(id _Nonnull (^ _Nonnull)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestBroadcastPart + requestAudioBroadcastPart:(id _Nonnull (^ _Nonnull)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestAudioBroadcastPart + requestVideoBroadcastPart:(id _Nonnull (^ _Nonnull)(int64_t, int64_t, int32_t, OngoingGroupCallRequestedVideoQuality, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestVideoBroadcastPart outgoingAudioBitrateKbit:(int32_t)outgoingAudioBitrateKbit videoContentType:(OngoingGroupCallVideoContentType)videoContentType enableNoiseSuppression:(bool)enableNoiseSuppression { @@ -1433,8 +1434,8 @@ private: .initialInputDeviceId = inputDeviceId.UTF8String, .initialOutputDeviceId = outputDeviceId.UTF8String, .videoCapture = [_videoCapturer getInterface], - .requestBroadcastPart = [requestBroadcastPart](int64_t timestampMilliseconds, int64_t durationMilliseconds, std::function completion) -> std::shared_ptr { - id task = requestBroadcastPart(timestampMilliseconds, durationMilliseconds, ^(OngoingGroupCallBroadcastPart * _Nullable part) { + .requestAudioBroadcastPart = [requestAudioBroadcastPart](int64_t timestampMilliseconds, int64_t durationMilliseconds, std::function completion) -> std::shared_ptr { + id task = requestAudioBroadcastPart(timestampMilliseconds, durationMilliseconds, ^(OngoingGroupCallBroadcastPart * _Nullable part) { tgcalls::BroadcastPart parsedPart; parsedPart.timestampMilliseconds = part.timestampMilliseconds; @@ -1461,13 +1462,67 @@ private: } parsedPart.status = mappedStatus; - parsedPart.oggData.resize(part.oggData.length); - [part.oggData getBytes:parsedPart.oggData.data() length:part.oggData.length]; + parsedPart.data.resize(part.oggData.length); + [part.oggData getBytes:parsedPart.data.data() length:part.oggData.length]; completion(std::move(parsedPart)); }); return std::make_shared(task); }, + .requestVideoBroadcastPart = [requestVideoBroadcastPart](int64_t timestampMilliseconds, int64_t durationMilliseconds, int32_t channelId, tgcalls::VideoChannelDescription::Quality quality, std::function completion) -> std::shared_ptr { + OngoingGroupCallRequestedVideoQuality mappedQuality; + switch (quality) { + case tgcalls::VideoChannelDescription::Quality::Thumbnail: { + mappedQuality = OngoingGroupCallRequestedVideoQualityThumbnail; + break; + } + case tgcalls::VideoChannelDescription::Quality::Medium: { + mappedQuality = OngoingGroupCallRequestedVideoQualityMedium; + break; + } + case tgcalls::VideoChannelDescription::Quality::Full: { + mappedQuality = OngoingGroupCallRequestedVideoQualityFull; + break; + } + default: { + mappedQuality = OngoingGroupCallRequestedVideoQualityThumbnail; + break; + } + } + id task = requestVideoBroadcastPart(timestampMilliseconds, durationMilliseconds, channelId, mappedQuality, ^(OngoingGroupCallBroadcastPart * _Nullable part) { + tgcalls::BroadcastPart parsedPart; + parsedPart.timestampMilliseconds = part.timestampMilliseconds; + + parsedPart.responseTimestamp = part.responseTimestamp; + + tgcalls::BroadcastPart::Status mappedStatus; + switch (part.status) { + case OngoingGroupCallBroadcastPartStatusSuccess: { + mappedStatus = tgcalls::BroadcastPart::Status::Success; + break; + } + case OngoingGroupCallBroadcastPartStatusNotReady: { + mappedStatus = tgcalls::BroadcastPart::Status::NotReady; + break; + } + case OngoingGroupCallBroadcastPartStatusResyncNeeded: { + mappedStatus = tgcalls::BroadcastPart::Status::ResyncNeeded; + break; + } + default: { + mappedStatus = tgcalls::BroadcastPart::Status::NotReady; + break; + } + } + parsedPart.status = mappedStatus; + + parsedPart.data.resize(part.oggData.length); + [part.oggData getBytes:parsedPart.data.data() length:part.oggData.length]; + + completion(std::move(parsedPart)); + }); + return std::make_shared(task); + }, .outgoingAudioBitrateKbit = outgoingAudioBitrateKbit, .disableOutgoingAudioProcessing = disableOutgoingAudioProcessing, .videoContentType = _videoContentType, diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 68eadd0cb3..b5a053d60a 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 68eadd0cb31ceedcfbf7f810dc86d2ebc7dabf9d +Subproject commit b5a053d60ab96a2f7857135adef4f1a9e8cfe775 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/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index d6ae34201c..38b4842493 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -94,19 +94,24 @@ public final class WallpaperBackgroundNode: ASDisplayNode { case .incoming: self.contentNode.image = graphics.incomingBubbleGradientImage if graphics.incomingBubbleGradientImage == nil { - self.contentNode.backgroundColor = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill + self.contentNode.backgroundColor = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill[0] } else { self.contentNode.backgroundColor = nil } - needsCleanBackground = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.alpha <= 0.99 || bubbleTheme.chat.message.incoming.bubble.withWallpaper.gradientFill.alpha <= 0.99 + needsCleanBackground = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) case .outgoing: - self.contentNode.image = graphics.outgoingBubbleGradientImage - if graphics.outgoingBubbleGradientImage == nil { - self.contentNode.backgroundColor = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill - } else { + if backgroundNode.outgoingBubbleGradientBackgroundNode != nil { + self.contentNode.image = nil self.contentNode.backgroundColor = nil + } else { + self.contentNode.image = graphics.outgoingBubbleGradientImage + if graphics.outgoingBubbleGradientImage == nil { + self.contentNode.backgroundColor = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill[0] + } else { + self.contentNode.backgroundColor = nil + } + needsCleanBackground = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) } - needsCleanBackground = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.alpha <= 0.99 || bubbleTheme.chat.message.outgoing.bubble.withWallpaper.gradientFill.alpha <= 0.99 case .free: self.contentNode.image = nil self.contentNode.backgroundColor = nil @@ -147,6 +152,16 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } + var gradientBackgroundSource: GradientBackgroundNode? = backgroundNode.gradientBackgroundNode + + if case .outgoing = self.bubbleType { + if let outgoingBubbleGradientBackgroundNode = backgroundNode.outgoingBubbleGradientBackgroundNode { + gradientBackgroundSource = outgoingBubbleGradientBackgroundNode + needsWallpaperBackground = false + needsGradientBackground = true + } + } + if needsWallpaperBackground { if self.cleanWallpaperNode == nil { let cleanWallpaperNode = ASImageNode() @@ -168,7 +183,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { } } - if needsGradientBackground, let gradientBackgroundNode = backgroundNode.gradientBackgroundNode { + if needsGradientBackground, let gradientBackgroundNode = gradientBackgroundSource { if self.gradientWallpaperNode == nil { let gradientWallpaperNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode) gradientWallpaperNode.frame = self.bounds @@ -277,6 +292,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { private var blurredBackgroundContents: UIImage? private var gradientBackgroundNode: GradientBackgroundNode? + private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? private let patternImageNode: ASImageNode private var isGeneratingPatternImage: Bool = false @@ -735,6 +751,11 @@ public final class WallpaperBackgroundNode: ASDisplayNode { gradientBackgroundNode.updateLayout(size: size, transition: transition) } + if let outgoingBubbleGradientBackgroundNode = self.outgoingBubbleGradientBackgroundNode { + transition.updateFrame(node: outgoingBubbleGradientBackgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: transition) + } + self.loadPatternForSizeIfNeeded(size: size, transition: transition) if isFirstLayout && !self.frame.isEmpty { @@ -744,6 +765,7 @@ public final class WallpaperBackgroundNode: ASDisplayNode { public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool = false) { self.gradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation) + self.outgoingBubbleGradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation) } public func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { @@ -751,6 +773,20 @@ public final class WallpaperBackgroundNode: ASDisplayNode { self.bubbleTheme = bubbleTheme self.bubbleCorners = bubbleCorners + if bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill.count >= 3 && bubbleTheme.chat.animateMessageColors { + if self.outgoingBubbleGradientBackgroundNode == nil { + let outgoingBubbleGradientBackgroundNode = GradientBackgroundNode(adjustSaturation: false) + if let size = self.validLayout { + outgoingBubbleGradientBackgroundNode.frame = CGRect(origin: CGPoint(), size: size) + outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: .immediate) + } + self.outgoingBubbleGradientBackgroundNode = outgoingBubbleGradientBackgroundNode + } + self.outgoingBubbleGradientBackgroundNode?.updateColors(colors: bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill) + } else if let _ = self.outgoingBubbleGradientBackgroundNode { + self.outgoingBubbleGradientBackgroundNode = nil + } + self.updateBubbles() } } @@ -789,14 +825,14 @@ public final class WallpaperBackgroundNode: ASDisplayNode { if graphics.incomingBubbleGradientImage != nil { return true } - if bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.alpha <= 0.99 { + if bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) { return !hasPlainWallpaper } case .outgoing: if graphics.outgoingBubbleGradientImage != nil { return true } - if bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.alpha <= 0.99 { + if bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) { return !hasPlainWallpaper } case .free: diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index d27283c899..24d4d8e419 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -17,7 +17,7 @@ import Svg import GradientBackground import GZip -public func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { +public func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) { let maybeFullSize: Signal @@ -168,7 +168,7 @@ public func wallpaperDatas(account: Account, accountManager: AccountManager, fil } } -public func wallpaperImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, blurred: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func wallpaperImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, blurred: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal = wallpaperDatas(account: account, accountManager: accountManager, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, thumbnail: thumbnail, onlyFullSize: onlyFullSize, autoFetchFullSize: autoFetchFullSize, synchronousLoad: synchronousLoad) return signal @@ -351,7 +351,7 @@ public struct PatternWallpaperArguments: TransformImageCustomArguments { } } -private func patternWallpaperDatas(account: Account, accountManager: AccountManager, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<(Data?, Bool), NoError> { +private func patternWallpaperDatas(account: Account, accountManager: AccountManager, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<(Data?, Bool), NoError> { var targetRepresentation: ImageRepresentationWithReference? switch mode { case .thumbnail: @@ -444,7 +444,7 @@ private func patternWallpaperDatas(account: Account, accountManager: AccountMana } } -public func patternWallpaperImage(account: Account, accountManager: AccountManager, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<((TransformImageArguments) -> DrawingContext?)?, NoError> { +public func patternWallpaperImage(account: Account, accountManager: AccountManager, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<((TransformImageArguments) -> DrawingContext?)?, NoError> { return patternWallpaperDatas(account: account, accountManager: accountManager, representations: representations, mode: mode, autoFetchFullSize: autoFetchFullSize) |> mapToSignal { fullSizeData, fullSizeComplete in return patternWallpaperImageInternal(fullSizeData: fullSizeData, fullSizeComplete: fullSizeComplete, mode: mode) @@ -862,7 +862,7 @@ public func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryM } } -public func telegramThemeData(account: Account, accountManager: AccountManager, reference: MediaResourceReference, synchronousLoad: Bool = false) -> Signal { +public func telegramThemeData(account: Account, accountManager: AccountManager, reference: MediaResourceReference, synchronousLoad: Bool = false) -> Signal { let maybeFetched = accountManager.mediaBox.resourceData(reference.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) return maybeFetched |> take(1) @@ -1001,16 +1001,22 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallp c.translateBy(x: -114.0, y: -32.0) let _ = try? drawSvgPath(c, path: "M98.0061174,0 C106.734138,0 113.82927,6.99200411 113.996965,15.6850616 L114,16 C114,24.836556 106.830179,32 98.0061174,32 L21.9938826,32 C18.2292665,32 14.7684355,30.699197 12.0362474,28.5221601 C8.56516444,32.1765452 -1.77635684e-15,31.9985981 -1.77635684e-15,31.9985981 C5.69252399,28.6991366 5.98604874,24.4421608 5.99940747,24.1573436 L6,24.1422468 L6,16 C6,7.163444 13.1698213,0 21.9938826,0 L98.0061174,0 ") - if incoming.fill.rgb != incoming.gradientFill.rgb { + if Set(incoming.fill.map(\.rgb)).count > 1 { c.clip() - - let gradientColors = [incoming.fill.withAlphaComponent(1.0), incoming.gradientFill.withAlphaComponent(1.0)].map { $0.cgColor } as CFArray - var locations: [CGFloat] = [0.0, 1.0] + + var colors: [CGColor] = [] + var locations: [CGFloat] = [] + for i in 0 ..< incoming.fill.count { + let t = CGFloat(i) / CGFloat(incoming.fill.count - 1) + locations.append(t) + colors.append(incoming.fill[i].cgColor) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)! c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 32.0), options: CGGradientDrawingOptions()) } else { - c.setFillColor(incoming.fill.cgColor) + c.setFillColor(incoming.fill[0].cgColor) c.setStrokeColor(incoming.stroke.cgColor) c.strokePath() @@ -1027,16 +1033,22 @@ public func drawThemeImage(context c: CGContext, theme: PresentationTheme, wallp c.translateBy(x: 0, y: -32.0) let _ = try? drawSvgPath(c, path: "M98.0061174,0 C106.734138,0 113.82927,6.99200411 113.996965,15.6850616 L114,16 C114,24.836556 106.830179,32 98.0061174,32 L21.9938826,32 C18.2292665,32 14.7684355,30.699197 12.0362474,28.5221601 C8.56516444,32.1765452 -1.77635684e-15,31.9985981 -1.77635684e-15,31.9985981 C5.69252399,28.6991366 5.98604874,24.4421608 5.99940747,24.1573436 L6,24.1422468 L6,16 C6,7.163444 13.1698213,0 21.9938826,0 L98.0061174,0 ") - if outgoing.fill.rgb != outgoing.gradientFill.rgb { + if Set(outgoing.fill.map(\.rgb)).count > 1 { c.clip() - - let gradientColors = [outgoing.fill.withAlphaComponent(1.0), outgoing.gradientFill.withAlphaComponent(1.0)].map { $0.cgColor } as CFArray - var locations: [CGFloat] = [0.0, 1.0] + + var colors: [CGColor] = [] + var locations: [CGFloat] = [] + for i in 0 ..< outgoing.fill.count { + let t = CGFloat(i) / CGFloat(outgoing.fill.count - 1) + locations.append(t) + colors.append(outgoing.fill[i].cgColor) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)! c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 32.0), options: CGGradientDrawingOptions()) } else { - c.setFillColor(outgoing.fill.cgColor) + c.setFillColor(outgoing.fill[0].cgColor) c.setStrokeColor(outgoing.stroke.cgColor) c.strokePath() @@ -1052,7 +1064,7 @@ public enum ThemeImageSource { case settings(TelegramThemeSettings) } -public func themeImage(account: Account, accountManager: AccountManager, source: ThemeImageSource, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func themeImage(account: Account, accountManager: AccountManager, source: ThemeImageSource, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let theme: Signal<(PresentationTheme?, Data?), NoError> switch source { @@ -1132,7 +1144,7 @@ public func themeImage(account: Account, accountManager: AccountManager, source: } } case let .settings(settings): - theme = .single((makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors.flatMap { (UIColor(argb: $0.top), UIColor(argb: $0.bottom)) }, wallpaper: settings.wallpaper, serviceBackgroundColor: nil, preview: false), nil)) + theme = .single((makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors, wallpaper: settings.wallpaper, serviceBackgroundColor: nil, preview: false), nil)) } enum WallpaperImage { @@ -1279,8 +1291,8 @@ public func themeImage(account: Account, accountManager: AccountManager, source: } } -public func themeIconImage(account: Account, accountManager: AccountManager, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), (UIColor, UIColor), (UIColor, UIColor), UIImage?, Int32?), 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? if case let .local(theme) = theme { @@ -1294,7 +1306,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the themeSignal = .single(makeDefaultPresentationTheme(reference: theme, serviceBackgroundColor: nil)) } else if case let .cloud(theme) = theme, let settings = theme.theme.settings { themeSignal = Signal { subscriber in - let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors.flatMap { (UIColor(argb: $0.top), UIColor(argb: $0.bottom)) }, wallpaper: settings.wallpaper, serviceBackgroundColor: nil, preview: false) + let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: .builtin(PresentationBuiltinThemeReference(baseTheme: settings.baseTheme)), accentColor: UIColor(argb: settings.accentColor), backgroundColors: [], bubbleColors: settings.messageColors, wallpaper: settings.wallpaper, serviceBackgroundColor: nil, preview: false) subscriber.putNext(theme) subscriber.putCompletion() @@ -1314,13 +1326,13 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the } colorsSignal = themeSignal - |> mapToSignal { theme -> Signal<((UIColor, UIColor?, [UInt32]), (UIColor, UIColor), (UIColor, UIColor), UIImage?, Int32?), NoError> in + |> mapToSignal { theme -> Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Int32?), NoError> in if let theme = theme { - var wallpaperSignal: Signal<((UIColor, UIColor?, [UInt32]), (UIColor, UIColor), (UIColor, UIColor), UIImage?, Int32?), NoError> = .complete() + var wallpaperSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Int32?), NoError> = .complete() var rotation: Int32? var backgroundColor: (UIColor, UIColor?, [UInt32]) - let incomingColor = (theme.chat.message.incoming.bubble.withoutWallpaper.fill, theme.chat.message.incoming.bubble.withoutWallpaper.gradientFill) - let outgoingColor = (theme.chat.message.outgoing.bubble.withoutWallpaper.fill, theme.chat.message.outgoing.bubble.withoutWallpaper.gradientFill) + let incomingColors = theme.chat.message.incoming.bubble.withoutWallpaper.fill + let outgoingColors = theme.chat.message.outgoing.bubble.withoutWallpaper.fill let wallpaper = wallpaper ?? theme.chat.defaultWallpaper switch wallpaper { case .builtin: @@ -1364,7 +1376,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the var convertedRepresentations: [ImageRepresentationWithReference] = [] convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: nil), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource))) return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) - |> mapToSignal { _, fullSizeData, complete -> Signal<((UIColor, UIColor?, [UInt32]), (UIColor, UIColor), (UIColor, UIColor), UIImage?, Int32?), NoError> in + |> mapToSignal { _, fullSizeData, complete -> Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Int32?), NoError> in guard complete, let fullSizeData = fullSizeData else { return .complete() } @@ -1374,9 +1386,9 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the if wallpaper.wallpaper.isPattern { if !file.settings.colors.isEmpty, let intensity = file.settings.intensity { if intensity < 0 { - return .single(((.black, nil, []), incomingColor, outgoingColor, nil, rotation)) + return .single(((.black, nil, []), incomingColors, outgoingColors, nil, rotation)) } else { - return .single((effectiveBackgroundColor, incomingColor, outgoingColor, nil, rotation)) + return .single((effectiveBackgroundColor, incomingColors, outgoingColors, nil, rotation)) } } else { return .complete() @@ -1385,13 +1397,13 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the return accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true) |> mapToSignal { _ in if let image = UIImage(data: fullSizeData) { - return .single((backgroundColor, incomingColor, outgoingColor, image, rotation)) + return .single((backgroundColor, incomingColors, outgoingColors, image, rotation)) } else { return .complete() } } } else if let image = UIImage(data: fullSizeData) { - return .single((backgroundColor, incomingColor, outgoingColor, image, rotation)) + return .single((backgroundColor, incomingColors, outgoingColors, image, rotation)) } else { return .complete() } @@ -1401,7 +1413,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the } } } - return .single((backgroundColor, incomingColor, outgoingColor, nil, rotation)) + return .single((backgroundColor, incomingColors, outgoingColors, nil, rotation)) |> then(wallpaperSignal) } else { return .complete() @@ -1456,21 +1468,71 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the } } - let incomingColors = [colors.1.0, colors.1.1] - let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors) - - let outgoingColors = [colors.2.0, colors.2.1] - let outgoing = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: outgoingColors) - c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0) c.scaleBy(x: 1.0, y: -1.0) c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0) - c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0)) + + let incomingColors = colors.1 + if emoticon { + let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0) + c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath) + c.clip() + + if incomingColors.count >= 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 @@ -1478,7 +1540,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager, the } } -public func wallpaperThumbnail(account: Account, accountManager: AccountManager, fileReference: FileMediaReference, wallpaper: TelegramWallpaper, synchronousLoad: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func wallpaperThumbnail(account: Account, accountManager: AccountManager, fileReference: FileMediaReference, wallpaper: TelegramWallpaper, synchronousLoad: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { switch wallpaper { case let .file(file): guard let thumbnail = smallestImageRepresentation(file.file.previewRepresentations) else { 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)