diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8dc703c92a..b364f3e1f6 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10474,12 +10474,6 @@ Sorry for the inconvenience."; "Channel.Info.Stats" = "Statistics and Boosts"; -"BoostGift.StartConfirmation.Title" = "Start Giveaway"; -"BoostGift.StartConfirmation.Text" = "Are you sure you want to start giveaway now?"; -"BoostGift.StartConfirmation.Start" = "Start"; - -"Channel.Info.Stats" = "Statistics and Boosts"; - "Conversation.FreeTranscriptionLimitTooltip_1" = "You have **%@** free voice transcription left this week."; "Conversation.FreeTranscriptionLimitTooltip_any" = "You have **%@** free voice transcriptions left this week."; @@ -10981,12 +10975,72 @@ Sorry for the inconvenience."; "ChatList.DeleteSavedPeerMyNotesConfirmation" = "Are you sure you want to delete all messages from %@?"; "ChatList.DeleteSavedPeerMyNotesConfirmationTitle" = "My Notes"; +"ChannelBoost.Title.Other" = "Help Upgrade This Channel"; +"ChannelBoost.Title.Current" = "Boost Channel"; + +"ChannelBoost.MoreBoostsNeeded.Text" = "**%1$@** needs %2$@ to unlock new features."; +"ChannelBoost.MoreBoostsNeeded.Boosts_1" = "**%@** more boost"; +"ChannelBoost.MoreBoostsNeeded.Boosts_any" = "**%@** more boosts"; +"ChannelBoost.MaxLevelReached.Text" = "**%1$@** reached **Level %2$@**."; + +"ChannelBoost.MoreBoostsNeeded.Boosted.Text" = "%@ needed to unlock new features."; + +"ContactList.Context.Delete" = "Delete Contact"; +"ContactList.Context.Select" = "Select"; + +"ContactList.DeleteConfirmationSingle" = "Delete Contact"; +"ContactList.DeleteConfirmation_1" = "Delete %@ Contact"; +"ContactList.DeleteConfirmation_any" = "Delete %@ Contacts"; + +"ContactList.DeletedContacts_1" = "Deleted %@ Contact"; +"ContactList.DeletedContacts_any" = "Deleted %@ Contacts"; + "Conversation.ForwardOptions.SenderNamesRemoved" = "Sender names removed"; "Login.Announce.Info" = "Notify people on Telegram who know my phone number that I signed up."; "Login.Announce.Notify" = "Notify"; "Login.Announce.DontNotify" = "Do Not Notify"; +"ChatList.ContextOpenGroup" = "Open Group"; + +"GroupBoost.Title.Other" = "Help Upgrade This Group"; +"GroupBoost.Title.Current" = "Boost Group"; + +"GroupBoost.EnableStoriesText" = "Your group needs %1$@ to enable posting stories.\n\nAsk your **Premium** members to boost your group with this link:"; +"GroupBoost.IncreaseLimitText" = "Your group needs %1$@ to post %2$@.\n\nAsk your **Premium** members to boost your group with this link:"; + +"BoostGift.Group.Description" = "Get more boosts for your group by gifting\nPremium to your subscribers."; + +"BoostGift.Group.AllMembers" = "All members"; +"BoostGift.Group.OnlyNewMembers" = "Only new members"; + +"BoostGift.Group.LimitMembersInfo" = "Choose if you want to limit the giveaway only to those who joined the group after the giveaway started."; +"BoostGift.Group.DateInfo" = "Choose when %1$@ of your group will be randomly selected to receive Telegram Premium."; +"BoostGift.Group.DateInfoMembers_1" = "%@ member"; +"BoostGift.Group.DateInfoMembers_any" = "%@ members"; + +"BoostGift.Group.GiveawayCreated.Text" = "Check your group's [Boosts]() to see how this giveaway boosted your group."; +"BoostGift.Group.PremiumGifted.Text" = "Check your groups's [Boosts]() to see how gifts boosted your group."; + +"BoostGift.Group.Members.Title" = "Gift Premium"; +"BoostGift.Group.Members.Subtitle" = "select up to %@ members"; +"BoostGift.Group.Members.SectionTitle" = "MEMBERS"; +"BoostGift.Group.Members.Joined" = "joined %@"; +"BoostGift.Group.Members.Search" = "Search Members"; +"BoostGift.Group.Members.MaximumReached" = "You can select maximum %@ members."; +"BoostGift.Group.Members.Save" = "Save Recipients"; + +"ChannelBoost.Table.Group.ProfileColor_1" = "%@ Color for Group Cover"; +"ChannelBoost.Table.Group.ProfileColor_any" = "%@ Colors for Group Cover"; +"ChannelBoost.Table.Group.ProfileLogo" = "Custom Logo for Group Cover"; +"ChannelBoost.Table.Group.Wallpaper_1" = "%@ Group Background"; +"ChannelBoost.Table.Group.Wallpaper_any" = "%@ Group Backgrounds"; +"ChannelBoost.Table.Group.CustomWallpaper" = "Custom Group Background"; + +"Premium.Group.BoostByGiftDescription" = "Boost your group by gifting your subscribers Telegram Premium. [Get boosts >]()"; + +"Conversation.BoostGroup" = "BOOST"; + "Premium.Stories.Quality.Title" = "Higher Quality"; "Premium.Stories.Quality.Text" = "View video stories in double the resolution."; @@ -11044,3 +11098,54 @@ Sorry for the inconvenience."; "Chat.BottomSearchPanel.DisplayModeList" = "List"; "Conversation.SendMessageErrorNonPremiumForbidden" = "Only Premium users can message %@"; + +"Notification.GiveawayStartedGroup" = "%1$@ just started a giveaway of Telegram Premium subscriptions for its members."; + +"Chat.Giveaway.Message.Group.Participants" = "All subscribers of this group:"; + +"Chat.Giveaway.Info.Group.RandomMembers_1" = "**%@** random member"; +"Chat.Giveaway.Info.Group.RandomMembers_any" = "**%@** random members"; + +"Chat.Giveaway.Info.Group.OngoingIntro" = "The giveaway is sponsored by the admins of **%1$@**, who acquired %2$@ for %3$@ for its members."; +"Chat.Giveaway.Info.Group.EndedIntro" = "The giveaway was sponsored by the admins of **%1$@**, who acquired %2$@ for %3$@ for its members."; + +"ChannelBoost.EnableGroupEmojiPackLevelText" = "Your group needs **Level %1$@** to set emoji pack."; + +"Premium.LastSeen" = "Last Seen Times"; +"Premium.LastSeenInfo" = "View the last seen and read times of others even if you hide yours."; +"Premium.LastSeen.Proceed" = "About Telegram Premium"; + +"Premium.MessagePrivacy" = "Message Privacy"; +"Premium.MessagePrivacyInfo" = "Restrict people you don't know from sending you messages."; +"Premium.MessagePrivacy.Proceed" = "About Telegram Premium"; + +"BoostGift.GroupsAndChannelsTitle" = "INCLUDED GROUPS AND CHANNELS"; +"BoostGift.GroupsAndChannelsInfo" = "Choose additional groups or channels users need to join to take part in the giveaway."; +"BoostGift.AddGroupOrChannel" = "Add Group or Channel"; + +"BoostGift.ChannelsAndGroupsTitle" = "INCLUDED CHANNELS AND GROUPS"; +"BoostGift.ChannelsAndGroupsInfo" = "Choose additional channels or groups users need to join to take part in the giveaway."; +"BoostGift.AddChannelOrGroup" = "Add Channel or Group"; + +"BoostGift.GroupBoosts_1" = "this group will receive %@ boost"; +"BoostGift.GroupBoosts_any" = "this group will receive %@ boosts"; + +"Stats.Boosts.PremiumMembers" = "Premium Members"; + +"GroupInfo.Permissions.DontRestrictBoosters" = "Do Not Restrict Boosters"; +"GroupInfo.Permissions.DontRestrictBoostersInfo" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages."; +"GroupInfo.Permissions.DontRestrictBoostersEnableInfo" = "Turn this on to always allow users who boosted your group to send messages and media."; + +"Notification.Boost.Times_1" = "%@ time"; +"Notification.Boost.Times_any" = "%@ times"; + +"Notification.Boost.Single" = "%1$@ boosted the group"; +"Notification.Boost.Multiple" = "%1$@ boosted the group %2$@"; + +"Notification.Boost.SingleYou" = "You boosted the group"; +"Notification.Boost.MultipleYou" = "You boosted the group %1$@"; + +"Contacts.SelectedContacts_1" = "%@ Selected"; +"Contacts.SelectedContacts_any" = "%@ Selected"; + +"Emoji.GroupEmoji" = "GROUP EMOJI"; diff --git a/submodules/AccountContext/Sources/PeerNameColors.swift b/submodules/AccountContext/Sources/PeerNameColors.swift index e8bce68a49..2b66687231 100644 --- a/submodules/AccountContext/Sources/PeerNameColors.swift +++ b/submodules/AccountContext/Sources/PeerNameColors.swift @@ -87,7 +87,8 @@ public class PeerNameColors: Equatable { profileStoryColors: [:], profileStoryDarkColors: [:], profileDisplayOrder: [], - nameColorsChannelMinRequiredBoostLevel: [:] + nameColorsChannelMinRequiredBoostLevel: [:], + nameColorsGroupMinRequiredBoostLevel: [:] ) } @@ -104,6 +105,7 @@ public class PeerNameColors: Equatable { public let profileDisplayOrder: [Int32] public let nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] + public let nameColorsGroupMinRequiredBoostLevel: [Int32: Int32] public func get(_ color: PeerNameColor, dark: Bool = false) -> Colors { if dark, let colors = self.darkColors[color.rawValue] { @@ -155,7 +157,8 @@ public class PeerNameColors: Equatable { profileStoryColors: [Int32: Colors], profileStoryDarkColors: [Int32: Colors], profileDisplayOrder: [Int32], - nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] + nameColorsChannelMinRequiredBoostLevel: [Int32: Int32], + nameColorsGroupMinRequiredBoostLevel: [Int32: Int32] ) { self.colors = colors self.darkColors = darkColors @@ -168,6 +171,7 @@ public class PeerNameColors: Equatable { self.profileStoryDarkColors = profileStoryDarkColors self.profileDisplayOrder = profileDisplayOrder self.nameColorsChannelMinRequiredBoostLevel = nameColorsChannelMinRequiredBoostLevel + self.nameColorsGroupMinRequiredBoostLevel = nameColorsGroupMinRequiredBoostLevel } public static func with(availableReplyColors: EngineAvailableColorOptions, availableProfileColors: EngineAvailableColorOptions) -> PeerNameColors { @@ -183,12 +187,16 @@ public class PeerNameColors: Equatable { var profileDisplayOrder: [Int32] = [] var nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] = [:] + var nameColorsGroupMinRequiredBoostLevel: [Int32: Int32] = [:] if !availableReplyColors.options.isEmpty { for option in availableReplyColors.options { if let requiredChannelMinBoostLevel = option.value.requiredChannelMinBoostLevel { nameColorsChannelMinRequiredBoostLevel[option.key] = requiredChannelMinBoostLevel } + if let requiredGroupMinBoostLevel = option.value.requiredGroupMinBoostLevel { + nameColorsGroupMinRequiredBoostLevel[option.key] = requiredGroupMinBoostLevel + } if let parsedLight = PeerNameColors.Colors(colors: option.value.light.background) { colors[option.key] = parsedLight @@ -249,7 +257,8 @@ public class PeerNameColors: Equatable { profileStoryColors: profileStoryColors, profileStoryDarkColors: profileStoryDarkColors, profileDisplayOrder: profileDisplayOrder, - nameColorsChannelMinRequiredBoostLevel: nameColorsChannelMinRequiredBoostLevel + nameColorsChannelMinRequiredBoostLevel: nameColorsChannelMinRequiredBoostLevel, + nameColorsGroupMinRequiredBoostLevel: nameColorsGroupMinRequiredBoostLevel ) } diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 8199157503..09ee8ce89a 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -68,6 +68,8 @@ public enum PremiumDemoSubject { case colors case wallpapers case messageTags + case lastSeen + case messagePrivacy } public enum PremiumLimitSubject { @@ -106,7 +108,13 @@ public struct PremiumConfiguration { minChannelProfileIconLevel: 7, minChannelEmojiStatusLevel: 8, minChannelWallpaperLevel: 9, - minChannelCustomWallpaperLevel: 10 + minChannelCustomWallpaperLevel: 10, + minGroupProfileIconLevel: 7, + minGroupEmojiStatusLevel: 8, + minGroupWallpaperLevel: 9, + minGroupCustomWallpaperLevel: 9, + minGroupEmojiPackLevel: 9, + minGroupAudioTranscriptionLevel: 9 ) } @@ -125,6 +133,13 @@ public struct PremiumConfiguration { public let minChannelWallpaperLevel: Int32 public let minChannelCustomWallpaperLevel: Int32 + public let minGroupProfileIconLevel: Int32 + public let minGroupEmojiStatusLevel: Int32 + public let minGroupWallpaperLevel: Int32 + public let minGroupCustomWallpaperLevel: Int32 + public let minGroupEmojiPackLevel: Int32 + public let minGroupAudioTranscriptionLevel: Int32 + fileprivate init( isPremiumDisabled: Bool, showPremiumGiftInAttachMenu: Bool, @@ -139,8 +154,13 @@ public struct PremiumConfiguration { minChannelProfileIconLevel: Int32, minChannelEmojiStatusLevel: Int32, minChannelWallpaperLevel: Int32, - minChannelCustomWallpaperLevel: Int32 - + minChannelCustomWallpaperLevel: Int32, + minGroupProfileIconLevel: Int32, + minGroupEmojiStatusLevel: Int32, + minGroupWallpaperLevel: Int32, + minGroupCustomWallpaperLevel: Int32, + minGroupEmojiPackLevel: Int32, + minGroupAudioTranscriptionLevel: Int32 ) { self.isPremiumDisabled = isPremiumDisabled self.showPremiumGiftInAttachMenu = showPremiumGiftInAttachMenu @@ -156,6 +176,12 @@ public struct PremiumConfiguration { self.minChannelEmojiStatusLevel = minChannelEmojiStatusLevel self.minChannelWallpaperLevel = minChannelWallpaperLevel self.minChannelCustomWallpaperLevel = minChannelCustomWallpaperLevel + self.minGroupProfileIconLevel = minGroupProfileIconLevel + self.minGroupEmojiStatusLevel = minGroupEmojiStatusLevel + self.minGroupWallpaperLevel = minGroupWallpaperLevel + self.minGroupCustomWallpaperLevel = minGroupCustomWallpaperLevel + self.minGroupEmojiPackLevel = minGroupEmojiPackLevel + self.minGroupAudioTranscriptionLevel = minGroupAudioTranscriptionLevel } public static func with(appConfiguration: AppConfiguration) -> PremiumConfiguration { @@ -178,7 +204,13 @@ public struct PremiumConfiguration { minChannelProfileIconLevel: get(data["channel_profile_bg_icon_level_min"]) ?? defaultValue.minChannelProfileIconLevel, minChannelEmojiStatusLevel: get(data["channel_emoji_status_level_min"]) ?? defaultValue.minChannelEmojiStatusLevel, minChannelWallpaperLevel: get(data["channel_wallpaper_level_min"]) ?? defaultValue.minChannelWallpaperLevel, - minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel + minChannelCustomWallpaperLevel: get(data["channel_custom_wallpaper_level_min"]) ?? defaultValue.minChannelCustomWallpaperLevel, + minGroupProfileIconLevel: get(data["group_profile_bg_icon_level_min "]) ?? defaultValue.minGroupProfileIconLevel, + minGroupEmojiStatusLevel: get(data["group_emoji_status_level_min"]) ?? defaultValue.minGroupEmojiStatusLevel, + minGroupWallpaperLevel: get(data["group_wallpaper_level_min"]) ?? defaultValue.minGroupWallpaperLevel, + minGroupCustomWallpaperLevel: get(data["group_custom_wallpaper_level_min"]) ?? defaultValue.minGroupCustomWallpaperLevel, + minGroupEmojiPackLevel: get(data["group_emoji_stickers_level_min"]) ?? defaultValue.minGroupEmojiPackLevel, + minGroupAudioTranscriptionLevel: get(data["group_transcribe_level_min"]) ?? defaultValue.minGroupAudioTranscriptionLevel ) } else { return defaultValue diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index fad73f715b..7bbc49b32c 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -982,6 +982,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { }, hideTranslationPanel: { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { + }, openBoostToUnrestrict: { }, updateHistoryFilter: { _ in }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in diff --git a/submodules/AudioWaveform/BUILD b/submodules/AudioWaveform/BUILD new file mode 100644 index 0000000000..4ffbb1fa8d --- /dev/null +++ b/submodules/AudioWaveform/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AudioWaveform", + module_name = "AudioWaveform", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/LegacyComponents:LegacyComponents", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ChatPresentationInterfaceState/Sources/AudioWaveForm.swift b/submodules/AudioWaveform/Sources/AudioWaveform.swift similarity index 100% rename from submodules/ChatPresentationInterfaceState/Sources/AudioWaveForm.swift rename to submodules/AudioWaveform/Sources/AudioWaveform.swift diff --git a/submodules/ChatInterfaceState/BUILD b/submodules/ChatInterfaceState/BUILD index c1850fcdaa..4d2c0bdd17 100644 --- a/submodules/ChatInterfaceState/BUILD +++ b/submodules/ChatInterfaceState/BUILD @@ -13,9 +13,11 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", + "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TextFormat:TextFormat", "//submodules/AccountContext:AccountContext", + "//submodules/AudioWaveform", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift index 9d7b0f68f4..1c94d2519d 100644 --- a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift +++ b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift @@ -1,9 +1,11 @@ import Foundation import UIKit +import Postbox import TelegramCore import TextFormat import AccountContext import SwiftSignalKit +import AudioWaveform public enum ChatTextInputMediaRecordingButtonMode: Int32 { case audio = 0 @@ -266,6 +268,171 @@ public struct ChatInterfaceHistoryScrollState: Codable, Equatable { } } +public enum ChatInterfaceMediaDraftState: Codable, Equatable { + enum DecodingError: Error { + case generic + } + + public struct Audio: Codable, Equatable { + public let resource: LocalFileMediaResource + public let fileSize: Int32 + public let duration: Int32 + public let waveform: AudioWaveform + + public init( + resource: LocalFileMediaResource, + fileSize: Int32, + duration: Int32, + waveform: AudioWaveform + ) { + self.resource = resource + self.fileSize = fileSize + self.duration = duration + self.waveform = waveform + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + let resourceData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: "r") + self.resource = LocalFileMediaResource(decoder: PostboxDecoder(buffer: MemoryBuffer(data: resourceData.data))) + + self.fileSize = try container.decode(Int32.self, forKey: "s") + self.duration = try container.decode(Int32.self, forKey: "d") + + let waveformData = try container.decode(Data.self, forKey: "wd") + let waveformPeak = try container.decode(Int32.self, forKey: "wp") + self.waveform = AudioWaveform(samples: waveformData, peak: waveformPeak) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(PostboxEncoder().encodeObjectToRawData(self.resource), forKey: "r") + try container.encode(self.fileSize, forKey: "s") + try container.encode(self.duration, forKey: "d") + try container.encode(self.waveform.samples, forKey: "wd") + try container.encode(self.waveform.peak, forKey: "wp") + } + + public static func ==(lhs: Audio, rhs: Audio) -> Bool { + if !lhs.resource.isEqual(to: rhs.resource) { + return false + } + if lhs.duration != rhs.duration { + return false + } + if lhs.fileSize != rhs.fileSize { + return false + } + if lhs.waveform != rhs.waveform { + return false + } + return true + } + } + + public struct Video: Codable, Equatable { + public let duration: Int32 + public let frames: [UIImage] + public let framesUpdateTimestamp: Double + public let trimRange: Range? + + public init( + duration: Int32, + frames: [UIImage], + framesUpdateTimestamp: Double, + trimRange: Range? + ) { + self.duration = duration + self.frames = frames + self.framesUpdateTimestamp = framesUpdateTimestamp + self.trimRange = trimRange + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.duration = try container.decode(Int32.self, forKey: "d") + self.frames = [] + self.framesUpdateTimestamp = try container.decode(Double.self, forKey: "fu") + if let trimLowerBound = try container.decodeIfPresent(Double.self, forKey: "tl"), let trimUpperBound = try container.decodeIfPresent(Double.self, forKey: "tu") { + self.trimRange = trimLowerBound ..< trimUpperBound + } else { + self.trimRange = nil + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(self.duration, forKey: "d") + try container.encode(self.framesUpdateTimestamp, forKey: "fu") + if let trimRange = self.trimRange { + try container.encode(trimRange.lowerBound, forKey: "tl") + try container.encode(trimRange.upperBound, forKey: "tu") + } + } + + public static func ==(lhs: Video, rhs: Video) -> Bool { + if lhs.duration != rhs.duration { + return false + } + if lhs.framesUpdateTimestamp != rhs.framesUpdateTimestamp { + return false + } + if lhs.trimRange != rhs.trimRange { + return false + } + return true + } + } + + case audio(Audio) + case video(Video) + + enum MediaType: Int32 { + case audio + case video + } + + public var contentType: EngineChatList.MediaDraftContentType { + switch self { + case .audio: + return .audio + case .video: + return .video + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + guard let mediaType = MediaType(rawValue: try container.decode(Int32.self, forKey: "t")) else { + throw DecodingError.generic + } + switch mediaType { + case .audio: + self = .audio(try container.decode(Audio.self, forKey: "a")) + case .video: + self = .video(try container.decode(Video.self, forKey: "v")) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + switch self { + case let .audio(audio): + try container.encode(MediaType.audio.rawValue, forKey: "t") + try container.encode(audio, forKey: "a") + case let .video(video): + try container.encode(MediaType.video.rawValue, forKey: "t") + try container.encode(video, forKey: "v") + } + } +} + public final class ChatInterfaceState: Codable, Equatable { public struct ReplyMessageSubject: Codable, Equatable { public var messageId: EngineMessage.Id @@ -295,6 +462,7 @@ public final class ChatInterfaceState: Codable, Equatable { public let messageActionsState: ChatInterfaceMessageActionsState public let historyScrollState: ChatInterfaceHistoryScrollState? public let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode + public let mediaDraftState: ChatInterfaceMediaDraftState? public let silentPosting: Bool public let inputLanguage: String? @@ -343,11 +511,12 @@ public final class ChatInterfaceState: Codable, Equatable { self.messageActionsState = ChatInterfaceMessageActionsState() self.historyScrollState = nil self.mediaRecordingMode = .audio + self.mediaDraftState = nil self.silentPosting = false self.inputLanguage = nil } - public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreviews: [String], replyMessageSubject: ReplyMessageSubject?, forwardMessageIds: [EngineMessage.Id]?, forwardOptionsState: ChatInterfaceForwardOptionsState?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { + public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreviews: [String], replyMessageSubject: ReplyMessageSubject?, forwardMessageIds: [EngineMessage.Id]?, forwardOptionsState: ChatInterfaceForwardOptionsState?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, mediaDraftState: ChatInterfaceMediaDraftState?, silentPosting: Bool, inputLanguage: String?) { self.timestamp = timestamp self.composeInputState = composeInputState self.composeDisableUrlPreviews = composeDisableUrlPreviews @@ -359,6 +528,7 @@ public final class ChatInterfaceState: Codable, Equatable { self.messageActionsState = messageActionsState self.historyScrollState = historyScrollState self.mediaRecordingMode = mediaRecordingMode + self.mediaDraftState = mediaDraftState self.silentPosting = silentPosting self.inputLanguage = inputLanguage } @@ -423,6 +593,12 @@ public final class ChatInterfaceState: Codable, Equatable { self.historyScrollState = try? container.decodeIfPresent(ChatInterfaceHistoryScrollState.self, forKey: "hss") self.mediaRecordingMode = ChatTextInputMediaRecordingButtonMode(rawValue: (try? container.decodeIfPresent(Int32.self, forKey: "mrm")) ?? 0) ?? .audio + + if let mediaDraftState = try? container.decodeIfPresent(ChatInterfaceMediaDraftState.self, forKey: "mds") { + self.mediaDraftState = mediaDraftState + } else { + self.mediaDraftState = nil + } self.silentPosting = ((try? container.decode(Int32.self, forKey: "sip")) ?? 0) != 0 self.inputLanguage = try? container.decodeIfPresent(String.self, forKey: "inputLanguage") @@ -471,6 +647,11 @@ public final class ChatInterfaceState: Codable, Equatable { try container.encodeNil(forKey: "hss") } try container.encode(self.mediaRecordingMode.rawValue, forKey: "mrm") + if let mediaDraftState = self.mediaDraftState { + try container.encode(mediaDraftState, forKey: "mds") + } else { + try container.encodeNil(forKey: "mds") + } try container.encode((self.silentPosting ? 1 : 0) as Int32, forKey: "sip") if let inputLanguage = self.inputLanguage { try container.encode(inputLanguage, forKey: "inputLanguage") @@ -502,6 +683,9 @@ public final class ChatInterfaceState: Codable, Equatable { if lhs.mediaRecordingMode != rhs.mediaRecordingMode { return false } + if lhs.mediaDraftState != rhs.mediaDraftState { + return false + } if lhs.silentPosting != rhs.silentPosting { return false } @@ -514,11 +698,11 @@ public final class ChatInterfaceState: Codable, Equatable { public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { let updatedComposeInputState = inputState - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedComposeDisableUrlPreviews(_ disableUrlPreviews: [String]) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: disableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: disableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { @@ -530,19 +714,19 @@ public final class ChatInterfaceState: Codable, Equatable { updatedComposeInputState = inputState } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedReplyMessageSubject(_ replyMessageSubject: ReplyMessageSubject?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedForwardMessageIds(_ forwardMessageIds: [EngineMessage.Id]?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedForwardOptionsState(_ forwardOptionsState: ChatInterfaceForwardOptionsState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedSelectedMessages(_ messageIds: [EngineMessage.Id]) -> ChatInterfaceState { @@ -553,7 +737,7 @@ public final class ChatInterfaceState: Codable, Equatable { for messageId in messageIds { selectedIds.insert(messageId) } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withToggledSelectedMessages(_ messageIds: [EngineMessage.Id], value: Bool) -> ChatInterfaceState { @@ -568,39 +752,43 @@ public final class ChatInterfaceState: Codable, Equatable { selectedIds.remove(messageId) } } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withoutSelectionState() -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) + } + + public func withUpdatedMediaDraftState(_ mediaDraftState: ChatInterfaceMediaDraftState?) -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedSilentPosting(_ silentPosting: Bool) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: silentPosting, inputLanguage: self.inputLanguage) } public func withUpdatedInputLanguage(_ inputLanguage: String?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, 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, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: inputLanguage) } public static func parse(_ state: OpaqueChatInterfaceState) -> ChatInterfaceState { @@ -621,13 +809,19 @@ public final class ChatInterfaceState: Codable, Equatable { let updatedState = f(previousState ?? ChatInterfaceState()) let updatedOpaqueData = try? EngineEncoder.encode(updatedState) - + + var mediaDraftState: MediaDraftState? + if let interfaceMediaDraftState = updatedState.mediaDraftState { + mediaDraftState = MediaDraftState(contentType: interfaceMediaDraftState.contentType, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)) + } + return engine.peers.setOpaqueChatInterfaceState( peerId: peerId, threadId: threadId, state: OpaqueChatInterfaceState( opaqueData: updatedOpaqueData, historyScrollMessageIndex: updatedState.historyScrollMessageIndex, + mediaDraftState: mediaDraftState, synchronizeableInputState: updatedState.synchronizeableInputState )) } diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index a1b840bd7d..4573379393 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -878,9 +878,15 @@ public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, |> deliverOnMainQueue).startStandalone(error: { error in switch error { case let .limitReached(count): + var replaceImpl: ((ViewController) -> Void)? let controller = PremiumLimitScreen(context: context, subject: .pinnedSavedPeers, count: Int32(count), action: { + let controller = PremiumIntroScreen(context: context, source: .pinnedChats) + replaceImpl?(controller) return true }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } parentController?.push(controller) default: break diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index a59c4b9f29..7bf10600ba 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2410,7 +2410,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if hasEmptyMark { if let componentView = self.chatListHeaderView() { - if let rightButtonView = componentView.rightButtonView { + if let rightButtonView = componentView.rightButtonViews["compose"] { let absoluteFrame = rightButtonView.convert(rightButtonView.bounds, to: self.view) let text: String = self.presentationData.strings.ChatList_EmptyListTooltip @@ -2903,8 +2903,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .archive)) }) }))) - } else if case .channel = peer { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_ContextOpenChannel, icon: { theme in + } else if case let .channel(channel) = peer { + let openTitle: String + switch channel.info { + case .broadcast: + openTitle = self.presentationData.strings.ChatList_ContextOpenChannel + case .group: + openTitle = self.presentationData.strings.ChatList_ContextOpenGroup + } + items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c.dismiss(completion: { diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 045f395c13..2f288b1a55 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -863,6 +863,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, + mediaDraftContentType: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, @@ -3735,6 +3736,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, + mediaDraftContentType: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index 865bdef36e..46fbf13029 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -199,6 +199,7 @@ public final class ChatListShimmerNode: ASDisplayNode { hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, + mediaDraftContentType: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index ced7bfff6e..4082b4e1ec 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -87,6 +87,7 @@ public enum ChatListItemContent { public var hasUnseenMentions: Bool public var hasUnseenReactions: Bool public var draftState: DraftState? + public var mediaDraftContentType: EngineChatList.MediaDraftContentType? public var inputActivities: [(EnginePeer, PeerInputActivity)]? public var promoInfo: ChatListNodeEntryPromoInfo? public var ignoreUnreadBadge: Bool @@ -109,6 +110,7 @@ public enum ChatListItemContent { hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, + mediaDraftContentType: EngineChatList.MediaDraftContentType?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, @@ -130,6 +132,7 @@ public enum ChatListItemContent { self.hasUnseenMentions = hasUnseenMentions self.hasUnseenReactions = hasUnseenReactions self.draftState = draftState + self.mediaDraftContentType = mediaDraftContentType self.inputActivities = inputActivities self.promoInfo = promoInfo self.ignoreUnreadBadge = ignoreUnreadBadge @@ -1648,6 +1651,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let isRemovedFromTotalUnreadCount: Bool let peerPresence: EnginePeer.Presence? let draftState: ChatListItemContent.DraftState? + let mediaDraftContentType: EngineChatList.MediaDraftContentType? let hasUnseenMentions: Bool let hasUnseenReactions: Bool let inputActivities: [(EnginePeer, PeerInputActivity)]? @@ -1699,6 +1703,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { return EnginePeer.Presence(status: presence.status, lastActivity: 0) } draftState = draftStateValue + mediaDraftContentType = peerData.mediaDraftContentType threadInfo = threadInfoValue hasUnseenMentions = hasUnseenMentionsValue hasUnseenReactions = hasUnseenReactionsValue @@ -1743,6 +1748,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { combinedReadState = nil isRemovedFromTotalUnreadCount = false draftState = nil + mediaDraftContentType = nil hasUnseenMentions = false hasUnseenReactions = false inputActivities = nil @@ -2004,7 +2010,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { chatListText = (text, messageText) } - if inlineAuthorPrefix == nil, let draftState = draftState { + if inlineAuthorPrefix == nil, let mediaDraftContentType { + hasDraft = true + authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor) + + //TODO:localize + switch mediaDraftContentType { + case .audio: + attributedText = NSAttributedString(string: "Voice Message", font: textFont, textColor: theme.messageTextColor) + case .video: + attributedText = NSAttributedString(string: "Video Message", font: textFont, textColor: theme.messageTextColor) + } + } else if inlineAuthorPrefix == nil, let draftState = draftState { hasDraft = true authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index f1b8176f9e..164feb1bdc 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -413,6 +413,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL hasUnseenMentions: hasUnseenMentions, hasUnseenReactions: hasUnseenReactions, draftState: draftState, + mediaDraftContentType: peerEntry.mediaDraftContentType, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, @@ -789,6 +790,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL hasUnseenMentions: hasUnseenMentions, hasUnseenReactions: hasUnseenReactions, draftState: draftState, + mediaDraftContentType: peerEntry.mediaDraftContentType, inputActivities: inputActivities, promoInfo: promoInfo, ignoreUnreadBadge: false, diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 8c39c392b2..d9e147d61a 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -97,6 +97,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { var readState: EnginePeerReadCounters? var isRemovedFromTotalUnreadCount: Bool var draftState: ChatListItemContent.DraftState? + var mediaDraftContentType: EngineChatList.MediaDraftContentType? var peer: EngineRenderedPeer var threadInfo: ChatListItemContent.ThreadInfo? var presence: EnginePeer.Presence? @@ -124,6 +125,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { readState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, draftState: ChatListItemContent.DraftState?, + mediaDraftContentType: EngineChatList.MediaDraftContentType?, peer: EngineRenderedPeer, threadInfo: ChatListItemContent.ThreadInfo?, presence: EnginePeer.Presence?, @@ -150,6 +152,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { self.readState = readState self.isRemovedFromTotalUnreadCount = isRemovedFromTotalUnreadCount self.draftState = draftState + self.mediaDraftContentType = mediaDraftContentType self.peer = peer self.threadInfo = threadInfo self.presence = presence @@ -221,6 +224,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else if (lhs.draftState != nil) != (rhs.draftState != nil) { return false } + if lhs.mediaDraftContentType != rhs.mediaDraftContentType { + return false + } if lhs.editing != rhs.editing { return false } @@ -596,7 +602,15 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, var result: [ChatListNodeEntry] = [] if !view.hasEarlier { + var existingPeerIds = Set() + for item in view.items { + existingPeerIds.insert(item.renderedPeer.peerId) + } + for contact in contacts { + if existingPeerIds.contains(contact.peer.id) { + continue + } result.append(.ContactEntry(ChatListNodeEntry.ContactEntryData( presentationData: state.presentationData, peer: contact.peer, @@ -663,7 +677,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, if let draft = entry.draft { draftState = ChatListItemContent.DraftState(draft: draft) } - + var hasActiveRevealControls = false if let peerId { hasActiveRevealControls = ChatListNodeState.ItemId(peerId: peerId, threadId: threadId) == state.peerIdWithRevealedOptions @@ -692,6 +706,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, + mediaDraftContentType: entry.mediaDraftContentType, peer: entry.renderedPeer, threadInfo: threadInfo, presence: entry.presence, @@ -749,6 +764,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, + mediaDraftContentType: nil, peer: EngineRenderedPeer(peerId: peer.0.id, peers: peers, associatedMedia: [:]), threadInfo: nil, presence: nil, @@ -782,6 +798,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, + mediaDraftContentType: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, @@ -835,6 +852,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, readState: item.item.readCounters, isRemovedFromTotalUnreadCount: item.item.isMuted, draftState: draftState, + mediaDraftContentType: item.item.mediaDraftContentType, peer: item.item.renderedPeer, threadInfo: item.item.threadData.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0.info, isOwnedByMe: $0.isOwnedByMe, isClosed: $0.isClosed, isHidden: $0.isHidden) }, presence: item.item.presence, diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index a559eef16b..c452612645 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -296,7 +296,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat autoremoveTimeout: nil, storyStats: nil, displayAsTopicList: false, - isPremiumRequiredToMessage: false + isPremiumRequiredToMessage: false, + mediaDraftContentType: nil )) } @@ -374,7 +375,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat autoremoveTimeout: nil, storyStats: nil, displayAsTopicList: false, - isPremiumRequiredToMessage: false + isPremiumRequiredToMessage: false, + mediaDraftContentType: nil )) } diff --git a/submodules/ChatPresentationInterfaceState/BUILD b/submodules/ChatPresentationInterfaceState/BUILD index 5d3ea70cf2..566e775d53 100644 --- a/submodules/ChatPresentationInterfaceState/BUILD +++ b/submodules/ChatPresentationInterfaceState/BUILD @@ -21,6 +21,7 @@ swift_library( "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ChatContextQuery", "//submodules/TooltipUI", + "//submodules/AudioWaveform", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index cdeb3d6ee8..efdb69cdfb 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -175,6 +175,7 @@ public final class ChatPanelInterfaceInteraction { public let openPremiumRequiredForMessaging: () -> Void public let updateHistoryFilter: ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void public let updateDisplayHistoryFilterAsList: (Bool) -> Void + public let openBoostToUnrestrict: () -> Void public let requestLayout: (ContainedViewLayoutTransition) -> Void public let chatController: () -> ViewController? public let statuses: ChatPanelInterfaceInteractionStatuses? @@ -284,6 +285,7 @@ public final class ChatPanelInterfaceInteraction { hideTranslationPanel: @escaping () -> Void, openPremiumGift: @escaping () -> Void, openPremiumRequiredForMessaging: @escaping () -> Void, + openBoostToUnrestrict: @escaping () -> Void, updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void, updateDisplayHistoryFilterAsList: @escaping (Bool) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, @@ -394,6 +396,7 @@ public final class ChatPanelInterfaceInteraction { self.hideTranslationPanel = hideTranslationPanel self.openPremiumGift = openPremiumGift self.openPremiumRequiredForMessaging = openPremiumRequiredForMessaging + self.openBoostToUnrestrict = openBoostToUnrestrict self.updateHistoryFilter = updateHistoryFilter self.updateDisplayHistoryFilterAsList = updateDisplayHistoryFilterAsList self.requestLayout = requestLayout @@ -512,6 +515,7 @@ public final class ChatPanelInterfaceInteraction { }, hideTranslationPanel: { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { + }, openBoostToUnrestrict: { }, updateHistoryFilter: { _ in }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index a0aadf7fee..cfff5e44d2 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -8,6 +8,7 @@ import TelegramUIPreferences import AccountContext import ChatInterfaceState import ChatContextQuery +import AudioWaveform public extension ChatLocation { var peerId: PeerId? { @@ -420,7 +421,6 @@ public final class ChatPresentationInterfaceState: Equatable { public let isArchived: Bool public let inputTextPanelState: ChatTextInputPanelState public let editMessageState: ChatEditInterfaceMessageState? - public let recordedMediaPreview: ChatRecordedMediaPreview? public let inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult] public let inputMode: ChatInputMode public let titlePanelContexts: [ChatTitlePanelContext] @@ -493,7 +493,6 @@ public final class ChatPresentationInterfaceState: Equatable { self.interfaceState = ChatInterfaceState() self.inputTextPanelState = ChatTextInputPanelState() self.editMessageState = nil - self.recordedMediaPreview = nil self.chatLocation = chatLocation self.renderedPeer = nil self.isNotAccessible = false @@ -570,7 +569,7 @@ public final class ChatPresentationInterfaceState: Equatable { self.isPremiumRequiredForMessaging = false } - public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: UrlPreview?, editingUrlPreview: UrlPreview?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, historyFilter: HistoryFilter?, displayHistoryFilterAsList: Bool, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReason?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasPlentyOfMessages: Bool, isPremium: Bool, premiumGiftOptions: [CachedPremiumGiftOption], suggestPremiumGift: Bool, forceInputCommandsHidden: Bool, voiceMessagesAvailable: Bool, customEmojiAvailable: Bool, threadData: ThreadData?, forumTopicData: ThreadData?, isGeneralThreadClosed: Bool?, translationState: ChatPresentationTranslationState?, replyMessage: Message?, accountPeerColor: AccountPeerColor?, savedMessagesTopicPeer: EnginePeer?, hasSearchTags: Bool, isPremiumRequiredForMessaging: Bool, hasSavedChats: Bool) { + public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: UrlPreview?, editingUrlPreview: UrlPreview?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, historyFilter: HistoryFilter?, displayHistoryFilterAsList: Bool, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReason?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasPlentyOfMessages: Bool, isPremium: Bool, premiumGiftOptions: [CachedPremiumGiftOption], suggestPremiumGift: Bool, forceInputCommandsHidden: Bool, voiceMessagesAvailable: Bool, customEmojiAvailable: Bool, threadData: ThreadData?, forumTopicData: ThreadData?, isGeneralThreadClosed: Bool?, translationState: ChatPresentationTranslationState?, replyMessage: Message?, accountPeerColor: AccountPeerColor?, savedMessagesTopicPeer: EnginePeer?, hasSearchTags: Bool, isPremiumRequiredForMessaging: Bool, hasSavedChats: Bool) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer @@ -581,7 +580,6 @@ public final class ChatPresentationInterfaceState: Equatable { self.isArchived = isArchived self.inputTextPanelState = inputTextPanelState self.editMessageState = editMessageState - self.recordedMediaPreview = recordedMediaPreview self.inputQueryResults = inputQueryResults self.inputMode = inputMode self.titlePanelContexts = titlePanelContexts @@ -679,9 +677,6 @@ public final class ChatPresentationInterfaceState: Equatable { if lhs.editMessageState != rhs.editMessageState { return false } - if lhs.recordedMediaPreview != rhs.recordedMediaPreview { - return false - } if lhs.inputQueryResults != rhs.inputQueryResults { return false } @@ -896,31 +891,31 @@ public final class ChatPresentationInterfaceState: Equatable { } public func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedIsNotAccessible(_ isNotAccessible: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedExplicitelyCanPinMessages(_ explicitelyCanPinMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedContactStatus(_ contactStatus: ChatContactStatus?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedHasBots(_ hasBots: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedIsArchived(_ isArchived: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { @@ -932,255 +927,251 @@ public final class ChatPresentationInterfaceState: Equatable { inputQueryResults.removeValue(forKey: queryKind) } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } - - public func updatedRecordedMediaPreview(_ recordedMediaPreview: ChatRecordedMediaPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) - } - + public func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPinnedMessage(_ pinnedMessage: ChatPinnedMessage?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPeerDiscussionId(_ peerDiscussionId: PeerId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedCallsAvailable(_ callsAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedCallsPrivate(_ callsPrivate: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedSlowmodeState(_ slowmodeState: ChatSlowmodeState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedUrlPreview(_ urlPreview: UrlPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedEditingUrlPreview(_ editingUrlPreview: UrlPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedHistoryFilter(_ historyFilter: HistoryFilter?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedDisplayHistoryFilterAsList(_ displayHistoryFilterAsList: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPresentationReady(_ presentationReady: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedDateTimeFormat(_ dateTimeFormat: PresentationDateTimeFormat) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedBubbleCorners(_ bubbleCorners: PresentationChatBubbleCorners) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedAutoremoveTimeout(_ autoremoveTimeout: Int32?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPendingUnpinnedAllMessages(_ pendingUnpinnedAllMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedActiveGroupCallInfo(_ activeGroupCallInfo: ChatActiveGroupCallInfo?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedHasActiveGroupCall(_ hasActiveGroupCall: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedImportState(_ importState: ChatPresentationImportState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedReportReason(_ reportReason: ReportReason?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedShowCommands(_ showCommands: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedHasBotCommands(_ hasBotCommands: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedShowSendAsPeers(_ showSendAsPeers: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedSendAsPeers(_ sendAsPeers: [SendAsPeer]?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedCurrentSendAsPeerId(_ currentSendAsPeerId: PeerId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedBotMenuButton(_ botMenuButton: BotMenuButton) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedShowWebView(_ showWebView: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedCopyProtectionEnabled(_ copyProtectionEnabled: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedHasPlentyOfMessages(_ hasPlentyOfMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedIsPremium(_ isPremium: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedPremiumGiftOptions(_ premiumGiftOptions: [CachedPremiumGiftOption]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedSuggestPremiumGift(_ suggestPremiumGift: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedForceInputCommandsHidden(_ forceInputCommandsHidden: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedCustomEmojiAvailable(_ customEmojiAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedThreadData(_ threadData: ThreadData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedForumTopicData(_ forumTopicData: ThreadData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedIsGeneralThreadClosed(_ isGeneralThreadClosed: Bool?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedTranslationState(_ translationState: ChatPresentationTranslationState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedReplyMessage(_ replyMessage: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedAccountPeerColor(_ accountPeerColor: AccountPeerColor?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedSavedMessagesTopicPeer(_ savedMessagesTopicPeer: EnginePeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedHasSearchTags(_ hasSearchTags: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedIsPremiumRequiredForMessaging(_ isPremiumRequiredForMessaging: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, hasSavedChats: self.hasSavedChats) } public func updatedHasSavedChats(_ hasSavedChats: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: hasSavedChats) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, hasSavedChats: hasSavedChats) } } diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index 57e319ce9c..6ed046685d 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -1069,7 +1069,7 @@ public struct Transition { } public func setShapeLayerFillColor(layer: CAShapeLayer, color: UIColor, completion: ((Bool) -> Void)? = nil) { - if let current = layer.layerTintColor, current == color.cgColor { + if let current = layer.fillColor, current == color.cgColor { completion?(true) return } diff --git a/submodules/ComponentFlow/Source/Components/HStack.swift b/submodules/ComponentFlow/Source/Components/HStack.swift index c174e77d01..1bb452d1ba 100644 --- a/submodules/ComponentFlow/Source/Components/HStack.swift +++ b/submodules/ComponentFlow/Source/Components/HStack.swift @@ -26,16 +26,21 @@ public final class HStack: CombinedComponent { let children = ChildMap(environment: ChildEnvironment.self, keyedBy: AnyHashable.self) return { context in - let updatedChildren = context.component.items.map { item in - return children[item.id].update( + var remainingWidth: CGFloat = context.availableSize.width + var updatedChildren: [_UpdatedChildComponent] = [] + + for item in context.component.items { + let child = children[item.id].update( component: item.component, environment: { context.environment[ChildEnvironment.self] }, - availableSize: context.availableSize, + availableSize: CGSize(width: remainingWidth, height: context.availableSize.height), transition: context.transition ) + updatedChildren.append(child) + remainingWidth -= context.component.spacing + child.size.width } - + var size = CGSize(width: 0.0, height: 0.0) for child in updatedChildren { size.width += child.size.width diff --git a/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift b/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift index 99543a244b..082666d28c 100644 --- a/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift +++ b/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift @@ -183,9 +183,6 @@ public final class MultilineTextWithEntitiesComponent: Component { } } - let size = self.textNode.updateLayout(availableSize) - self.textNode.frame = CGRect(origin: .zero, size: size) - self.textNode.visibility = true if let context = component.context, let animationCache = component.animationCache, let animationRenderer = component.animationRenderer, let placeholderColor = component.placeholderColor { self.textNode.arguments = TextNodeWithEntities.Arguments( @@ -197,6 +194,9 @@ public final class MultilineTextWithEntitiesComponent: Component { ) } + let size = self.textNode.updateLayout(availableSize) + self.textNode.frame = CGRect(origin: .zero, size: size) + return size } } diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD index e9e43ff651..1ce3cafb91 100644 --- a/submodules/ContactListUI/BUILD +++ b/submodules/ContactListUI/BUILD @@ -44,6 +44,7 @@ swift_library( "//submodules/ComponentFlow", "//submodules/TooltipUI", "//submodules/UndoUI", + "//submodules/TelegramIntents", ], visibility = [ "//visibility:public", diff --git a/submodules/ContactListUI/Sources/ContactContextMenus.swift b/submodules/ContactListUI/Sources/ContactContextMenus.swift index 40d1918d0f..216be65305 100644 --- a/submodules/ContactListUI/Sources/ContactContextMenus.swift +++ b/submodules/ContactListUI/Sources/ContactContextMenus.swift @@ -125,7 +125,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con if canStartSecretChat { items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_StartSecretChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) }, action: { _, f in let _ = (context.engine.peers.mostRecentSecretChat(id: peerId) - |> deliverOnMainQueue).start(next: { currentPeerId in + |> deliverOnMainQueue).start(next: { [weak contactsController] currentPeerId in if let currentPeerId = currentPeerId { let _ = (context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: currentPeerId) @@ -228,6 +228,25 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con f(.default) }))) } + + items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak contactsController] _, f in + if let contactsController { + contactsController.requestDeleteContacts(peerIds: [peerId]) + } + f(.dismissWithoutContent) + }))) + + items.append(.separator) + + items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Select, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) + }, action: { [weak contactsController] _, f in + if let contactsController { + contactsController.beginSelection(peerId: peerId) + } + f(.default) + }))) + return items } } diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 58f6510707..4d2ca355f5 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -816,6 +816,13 @@ public final class ContactListNode: ASDisplayNode { } } + private let pendingRemovalPeerIdsPromise = ValuePromise>(Set()) + private var pendingRemovalPeerIds = Set() { + didSet { + self.pendingRemovalPeerIdsPromise.set(self.pendingRemovalPeerIds) + } + } + private var interaction: ContactListNodeInteraction? private var enableUpdatesValue = false @@ -978,6 +985,7 @@ public final class ContactListNode: ASDisplayNode { let processingQueue = Queue() let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil) let previousSelectionState = Atomic(value: nil) + let previousPendingRemovalPeerIds = Atomic?>(value: nil) let interaction = ContactListNodeInteraction(activateSearch: { [weak self] in self?.activateSearch?() @@ -1074,6 +1082,7 @@ public final class ContactListNode: ASDisplayNode { let context = self.context var firstTime: Int32 = 1 let selectionStateSignal = self.selectionStatePromise.get() + let pendingRemovalPeerIdsSignal = self.pendingRemovalPeerIdsPromise.get() let transition: Signal let presentationDataPromise = self.presentationDataPromise @@ -1231,8 +1240,8 @@ public final class ContactListNode: ASDisplayNode { peerRequiresPremiumForMessaging = .single([:]) } - return combineLatest(accountPeer, foundPeers.get(), peerRequiresPremiumForMessaging, foundDeviceContacts, selectionStateSignal, presentationDataPromise.get()) - |> mapToQueue { accountPeer, foundPeers, peerRequiresPremiumForMessaging, deviceContacts, selectionState, presentationData -> Signal in + return combineLatest(accountPeer, foundPeers.get(), peerRequiresPremiumForMessaging, foundDeviceContacts, selectionStateSignal, pendingRemovalPeerIdsSignal, presentationDataPromise.get()) + |> mapToQueue { accountPeer, foundPeers, peerRequiresPremiumForMessaging, deviceContacts, selectionState, pendingRemovalPeerIds, presentationData -> Signal in let localPeersAndStatuses = foundPeers.foundLocalContacts let remotePeers = foundPeers.foundRemoteContacts @@ -1281,12 +1290,13 @@ public final class ContactListNode: ASDisplayNode { } for peer in localPeersAndStatuses.0 { - if !existingPeerIds.contains(peer.peer.id) { - existingPeerIds.insert(peer.peer.id) - peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { - existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) - } + if existingPeerIds.contains(peer.peer.id) || pendingRemovalPeerIds.contains(peer.peer.id) { + continue + } + existingPeerIds.insert(peer.peer.id) + peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers)) + if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } } for peer in remotePeers.0 { @@ -1315,12 +1325,13 @@ public final class ContactListNode: ASDisplayNode { } if matches { - if !existingPeerIds.contains(peer.peer.id) { - existingPeerIds.insert(peer.peer.id) - peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { - existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) - } + if existingPeerIds.contains(peer.peer.id) || pendingRemovalPeerIds.contains(peer.peer.id) { + continue + } + existingPeerIds.insert(peer.peer.id) + peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) + if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } } } @@ -1350,12 +1361,13 @@ public final class ContactListNode: ASDisplayNode { } if matches { - if !existingPeerIds.contains(peer.peer.id) { - existingPeerIds.insert(peer.peer.id) - peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { - existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) - } + if existingPeerIds.contains(peer.peer.id) || pendingRemovalPeerIds.contains(peer.peer.id) { + continue + } + existingPeerIds.insert(peer.peer.id) + peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) + if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } } } @@ -1446,13 +1458,14 @@ public final class ContactListNode: ASDisplayNode { self.contactPeersViewPromise.get(), chatListSignal, selectionStateSignal, + pendingRemovalPeerIdsSignal, presentationDataPromise.get(), contactsAuthorization.get(), contactsWarningSuppressed.get(), self.storySubscriptions.get(), recentPeers ) - |> mapToQueue { view, chatListPeers, selectionState, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, recentPeers -> Signal in + |> mapToQueue { view, chatListPeers, selectionState, pendingRemovalPeerIds, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, recentPeers -> Signal in let signal = deferred { () -> Signal in if !view.2.isEmpty { context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys)) @@ -1487,7 +1500,7 @@ public final class ContactListNode: ASDisplayNode { return false } } - return !existingPeerIds.contains(peer.id) + return !existingPeerIds.contains(peer.id) && !pendingRemovalPeerIds.contains(peer.id) default: return true } @@ -1505,6 +1518,7 @@ public final class ContactListNode: ASDisplayNode { let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers, interaction: interaction) let previous = previousEntries.swap(entries) let previousSelection = previousSelectionState.swap(selectionState) + let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds) var hadPermissionInfo = false if let previous = previous { @@ -1526,6 +1540,8 @@ public final class ContactListNode: ASDisplayNode { let animation: ContactListAnimation if (previousSelection == nil) != (selectionState == nil) { animation = .insertion + } else if previousPendingRemovalPeerIds != pendingRemovalPeerIds { + animation = .insertion } else if hadPermissionInfo != hasPermissionInfo { animation = .insertion } else { @@ -1652,6 +1668,13 @@ public final class ContactListNode: ASDisplayNode { } } + public func updatePendingRemovalPeerIds(_ f: (Set) -> Set) { + let updatedPendingRemovalPeerIds = f(self.pendingRemovalPeerIds) + if updatedPendingRemovalPeerIds != self.pendingRemovalPeerIds { + self.pendingRemovalPeerIds = updatedPendingRemovalPeerIds + } + } + private var previousStoriesInset: CGFloat? public var ignoreStoryInsetAdjustment: Bool = false diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index bd5915e0b1..5f95d9bf26 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -21,6 +21,8 @@ import ContextUI import QrCodeUI import StoryContainerScreen import ChatListHeaderComponent +import TelegramIntents +import UndoUI private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController @@ -116,6 +118,8 @@ public class ContactsController: ViewController { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? private var authorizationDisposable: Disposable? + private var selectionDisposable: Disposable? + private var actionDisposable = MetaDisposable() private let sortOrderPromise = Promise() private let isInVoiceOver = ValuePromise(false) @@ -230,6 +234,8 @@ public class ContactsController: ViewController { deinit { self.presentationDataDisposable?.dispose() self.authorizationDisposable?.dispose() + self.actionDisposable.dispose() + self.selectionDisposable?.dispose() } private func updateThemeAndStrings() { @@ -339,8 +345,21 @@ public class ContactsController: ViewController { self?.activateSearch() } - self.contactsNode.contactListNode.openPeer = { peer, _ in - openPeer(peer, false) + self.contactsNode.contactListNode.openPeer = { [weak self] peer, _ in + guard let self else { + return + } + if let _ = self.contactsNode.contactListNode.selectionState { + self.contactsNode.contactListNode.updateSelectionState({ current in + if let updatedState = current?.withToggledPeerId(peer.id), !updatedState.selectedPeerIndices.isEmpty { + return updatedState + } else { + return nil + } + }) + } else { + openPeer(peer, false) + } } self.contactsNode.requestAddContact = { [weak self] phoneNumber in @@ -467,9 +486,46 @@ public class ContactsController: ViewController { self?.presentSortMenu(sourceView: sourceNode.view, gesture: gesture) } + let previousToolbarValue = Atomic(value: nil) + self.selectionDisposable = (self.contactsNode.contactListNode.selectionStateSignal + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self, let layout = self.validLayout else { + return + } + + let toolbar: Toolbar? + if let state, state.selectedPeerIndices.count > 0 { + toolbar = Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: self.presentationData.strings.ContactList_DeleteConfirmation(Int32(state.selectedPeerIndices.count)), isEnabled: true, color: .custom(self.presentationData.theme.actionSheet.destructiveActionTextColor))) + } else { + toolbar = nil + } + + let _ = self.contactsNode.updateNavigationBar(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + + var transition: ContainedViewLayoutTransition = .immediate + let previousToolbar = previousToolbarValue.swap(toolbar) + if (previousToolbar == nil) != (toolbar == nil) { + transition = .animated(duration: 0.4, curve: .spring) + } + self.setToolbar(toolbar, transition: transition) + }) + self.displayNodeDidLoad() } + override public func toolbarActionSelected(action: ToolbarActionOption) { + guard case .middle = action, let selectionState = self.contactsNode.contactListNode.selectionState else { + return + } + var peerIds: [EnginePeer.Id] = [] + for contactPeerId in selectionState.selectedPeerIndices.keys { + if case let .peer(peerId) = contactPeerId { + peerIds.append(peerId) + } + } + self.requestDeleteContacts(peerIds: peerIds) + } + override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -576,6 +632,123 @@ public class ContactsController: ViewController { self.presentInGlobalOverlay(contextController) } + public func beginSelection(peerId: EnginePeer.Id) { + self.contactsNode.contactListNode.updateSelectionState { _ in + return ContactListNodeGroupSelectionState().withToggledPeerId(.peer(peerId)) + } + } + + public func requestDeleteContacts(peerIds: [EnginePeer.Id]) { + guard !peerIds.isEmpty else { + return + } + let actionSheet = ActionSheetController(presentationData: self.presentationData) + var items: [ActionSheetItem] = [] + + let actionTitle: String + if peerIds.count > 1 { + actionTitle = self.presentationData.strings.ContactList_DeleteConfirmation(Int32(peerIds.count)) + } else { + actionTitle = self.presentationData.strings.ContactList_DeleteConfirmationSingle + } + + items.append(ActionSheetButtonItem(title: actionTitle, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + guard let self else { + return + } + + self.contactsNode.contactListNode.updateSelectionState { _ in + return nil + } + + self.contactsNode.contactListNode.updatePendingRemovalPeerIds { state in + var state = state + for peerId in peerIds { + state.insert(peerId) + } + return state + } + + let text = self.presentationData.strings.ContactList_DeletedContacts(Int32(peerIds.count)) + + self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in + guard let self else { + return false + } + if value == .commit { + let presentationData = self.presentationData + + let deleteContactsFromDevice: Signal + if let contactDataManager = self.context.sharedContext.contactDataManager { + deleteContactsFromDevice = contactDataManager.deleteContactWithAppSpecificReference(peerId: peerIds.first!) + } else { + deleteContactsFromDevice = .complete() + } + + var deleteSignal = self.context.engine.contacts.deleteContacts(peerIds: peerIds) + |> then(deleteContactsFromDevice) + + let progressSignal = Signal { [weak self] subscriber in + guard let self else { + return EmptyDisposable + } + let statusController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) + self.present(statusController, in: .window(.root)) + return ActionDisposable { [weak statusController] in + Queue.mainQueue().async() { + statusController?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + deleteSignal = deleteSignal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + + for peerId in peerIds { + deleteSendMessageIntents(peerId: peerId) + } + + self.contactsNode.contactListNode.updatePendingRemovalPeerIds { state in + var state = state + for peerId in peerIds { + state.remove(peerId) + } + return state + } + return true + } else if value == .undo { + self.contactsNode.contactListNode.updatePendingRemovalPeerIds { state in + var state = state + for peerId in peerIds { + state.remove(peerId) + } + return state + } + return true + } + return false + }), in: .current) + })) + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + self.present(actionSheet, in: .window(.root)) + } + @objc func addPressed() { let _ = (DeviceAccess.authorizationStatus(subject: .contacts) |> take(1) diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 02bc45b06e..6105a64ba9 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -297,16 +297,31 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { return false } - private func updateNavigationBar(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) { + func updateNavigationBar(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) { let tabsNode: ASDisplayNode? = nil let tabsNodeIsSearch = false - let primaryContent = ChatListHeaderComponent.Content( - title: self.presentationData.strings.Contacts_Title, - navigationBackTitle: nil, - titleComponent: nil, - chatListTitle: NetworkStatusTitle(text: self.presentationData.strings.Contacts_Title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil), - leftButton: AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent( + let title: String + let leftButton: AnyComponentWithIdentity? + let rightButtons: [AnyComponentWithIdentity] + + if let selectionState = self.contactListNode.selectionState { + title = self.presentationData.strings.Contacts_SelectedContacts(Int32(selectionState.selectedPeerIndices.count)) + leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] sourceView in + guard let self else { + return + } + self.contactListNode.updateSelectionState { _ in + return nil + } + } + ))) + rightButtons = [] + } else { + title = self.presentationData.strings.Contacts_Title + leftButton = AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent( content: .text(title: self.presentationData.strings.Contacts_Sort, isBold: false), pressed: { [weak self] sourceView in guard let self else { @@ -315,8 +330,8 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.controller?.presentSortMenu(sourceView: sourceView, gesture: nil) } - ))), - rightButtons: [AnyComponentWithIdentity(id: "add", component: AnyComponent(NavigationButtonComponent( + ))) + rightButtons = [AnyComponentWithIdentity(id: "add", component: AnyComponent(NavigationButtonComponent( content: .icon(imageName: "Chat List/AddIcon"), pressed: { [weak self] _ in guard let self else { @@ -324,7 +339,16 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } self.controller?.addPressed() } - )))], + )))] + } + + let primaryContent = ChatListHeaderComponent.Content( + title: self.presentationData.strings.Contacts_Title, + navigationBackTitle: nil, + titleComponent: nil, + chatListTitle: NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil), + leftButton: leftButton, + rightButtons: rightButtons, backTitle: nil, backPressed: nil ) diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 69e8d66cb1..eeac765ad8 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -1435,7 +1435,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } func replace(item: ContextControllerActionsStackItem, animated: Bool?) { - if let item = item as? ContextControllerActionsListStackItem, let topContainer = self.itemContainers.last, let topItem = topContainer.item as? ContextControllerActionsListStackItem, let topId = topItem.id, let id = item.id, topId == id, item.items.count == topItem.items.count { + if let item = item as? ContextControllerActionsListStackItem, let topContainer = self.itemContainers.first, let topItem = topContainer.item as? ContextControllerActionsListStackItem, let topId = topItem.id, let id = item.id, topId == id, item.items.count == topItem.items.count { if let topNode = topContainer.node as? ContextControllerActionsListStackItem.Node { var matches = true for i in 0 ..< item.items.count { diff --git a/submodules/ContextUI/Sources/ContextSourceContainer.swift b/submodules/ContextUI/Sources/ContextSourceContainer.swift index d6c7ec6e7e..9c41f964a2 100644 --- a/submodules/ContextUI/Sources/ContextSourceContainer.swift +++ b/submodules/ContextUI/Sources/ContextSourceContainer.swift @@ -32,7 +32,7 @@ final class ContextSourceContainer: ASDisplayNode { var delayLayoutUpdate: Bool = false var isAnimatingOut: Bool = false - let itemsDisposable = MetaDisposable() + var itemsDisposables = DisposableSet() let ready = Promise() private let contentReady = Promise() @@ -226,7 +226,7 @@ final class ContextSourceContainer: ASDisplayNode { self._presentationNode = presentationNode } - self.itemsDisposable.set((items |> deliverOnMainQueue).start(next: { [weak self] items in + self.itemsDisposables.add((items |> deliverOnMainQueue).start(next: { [weak self] items in guard let self else { return } @@ -237,7 +237,7 @@ final class ContextSourceContainer: ASDisplayNode { } deinit { - self.itemsDisposable.dispose() + self.itemsDisposables.dispose() } func animateIn() { @@ -274,7 +274,9 @@ final class ContextSourceContainer: ASDisplayNode { } func setItems(items: Signal, animated: Bool) { - self.itemsDisposable.set((items + self.itemsDisposables.dispose() + self.itemsDisposables = DisposableSet() + self.itemsDisposables.add((items |> deliverOnMainQueue).start(next: { [weak self] items in guard let self else { return @@ -288,7 +290,7 @@ final class ContextSourceContainer: ASDisplayNode { } func pushItems(items: Signal) { - self.itemsDisposable.set((items + self.itemsDisposables.add((items |> deliverOnMainQueue).start(next: { [weak self] items in guard let self else { return @@ -298,6 +300,7 @@ final class ContextSourceContainer: ASDisplayNode { } func popItems() { + self.itemsDisposables.removeLast() self.presentationNode.popItems() } diff --git a/submodules/DrawingUI/Sources/DrawingPenTool.swift b/submodules/DrawingUI/Sources/DrawingPenTool.swift index 5fabb02c1c..ee6880cbd2 100644 --- a/submodules/DrawingUI/Sources/DrawingPenTool.swift +++ b/submodules/DrawingUI/Sources/DrawingPenTool.swift @@ -672,6 +672,7 @@ final class PenTool: DrawingElement { return self.start.location.distance(to: self.end.location) } } + private func smoothPoints(_ input: SmootherInput) -> [Point] { let segmentDistance: CGFloat = 6.0 let distance = input.distance diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index b3f2ebcbbb..f56914ff3f 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -1115,6 +1115,7 @@ public class StickerPickerScreen: ViewController { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1169,6 +1170,7 @@ public class StickerPickerScreen: ViewController { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1199,6 +1201,7 @@ public class StickerPickerScreen: ViewController { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1440,6 +1443,7 @@ public class StickerPickerScreen: ViewController { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1470,6 +1474,7 @@ public class StickerPickerScreen: ViewController { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 6b7beda409..127cd008a7 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -48,6 +48,7 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem { let editing: ItemListStickerPackItemEditing let enabled: Bool let playAnimatedStickers: Bool + let style: ItemListStyle public let sectionId: ItemListSectionId let action: (() -> Void)? let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void @@ -55,7 +56,7 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem { let removePack: () -> Void let toggleSelected: () -> Void - public init(presentationData: ItemListPresentationData, context: AccountContext, packInfo: StickerPackCollectionInfo, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) { + public init(presentationData: ItemListPresentationData, context: AccountContext, packInfo: StickerPackCollectionInfo, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) { self.presentationData = presentationData self.context = context self.packInfo = packInfo @@ -66,6 +67,7 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem { self.editing = editing self.enabled = enabled self.playAnimatedStickers = playAnimatedStickers + self.style = style self.sectionId = sectionId self.action = action self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions @@ -410,8 +412,20 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let verticalInset: CGFloat = 11.0 let titleSpacing: CGFloat = 2.0 - let insets = itemListNeighborsGroupedInsets(neighbors, params) let separatorHeight = UIScreenPixel + let insets: UIEdgeInsets + let itemBackgroundColor: UIColor + let itemSeparatorColor: UIColor + switch item.style { + case .plain: + itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor + itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor + insets = itemListNeighborsPlainInsets(neighbors) + case .blocks: + itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor + insets = itemListNeighborsGroupedInsets(neighbors, params) + } var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)? var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)? @@ -543,9 +557,9 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } if let _ = updatedTheme { - strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor - strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + strongSelf.topStripeNode.backgroundColor = itemSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor + strongSelf.backgroundNode.backgroundColor = itemBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor } diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 3ce8365f56..b8dcdcf313 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -585,7 +585,11 @@ open class ItemListControllerNode: ASDisplayNode { if let headerItemNode = self.headerItemNode { let headerHeight = headerItemNode.updateLayout(layout: layout, transition: transition) headerItemNode.frame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: 56.0)) - insets.top += headerHeight + if headerHeight > 300.0 { + insets.top = headerHeight + } else { + insets.top += headerHeight + } } var footerHeight: CGFloat = 0.0 @@ -986,7 +990,7 @@ open class ItemListControllerNode: ASDisplayNode { if updateSearchItem { self.requestLayout?(.animated(duration: 0.3, curve: .spring)) - } else if updateToolbarItem || updateFooterItem, let (layout, navigationBarHeight, additionalInsets) = self.validLayout { + } else if updateToolbarItem || updateHeaderItem || updateFooterItem, let (layout, navigationBarHeight, additionalInsets) = self.validLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .spring), additionalInsets: additionalInsets) } } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 6823185ab5..d167cd1139 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1831,7 +1831,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { guard let self else { return } - let items = items.filter { $0.count > 0 } + let items = items.filter { $0.count > 0 || $0.collection.assetCollectionSubtype == .smartAlbumAllHidden } var dismissImpl: (() -> Void)? let content: ContextControllerItemsContent = MediaGroupsContextMenuContent( context: self.context, diff --git a/submodules/PeerInfoUI/BUILD b/submodules/PeerInfoUI/BUILD index 94da92e765..51a5895d90 100644 --- a/submodules/PeerInfoUI/BUILD +++ b/submodules/PeerInfoUI/BUILD @@ -77,6 +77,7 @@ swift_library( "//submodules/Components/ReactionImageComponent:ReactionImageComponent", "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", "//submodules/TelegramUI/Components/SendInviteLinkScreen", + "//submodules/TelegramUI/Components/GroupStickerPackSetupController", ], visibility = [ "//visibility:public", diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 0c4164e3c5..bdaeb73de9 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -33,9 +33,10 @@ private final class ChannelPermissionsControllerArguments { let presentConversionToBroadcastGroup: () -> Void let openChannelExample: () -> Void let updateSlowmode: (Int32) -> Void + let updateUnrestrictBoosters: (Int32) -> Void let toggleIsOptionExpanded: (TelegramChatBannedRightsFlags) -> Void - init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) { + init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, updateUnrestrictBoosters: @escaping (Int32) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) { self.context = context self.updatePermission = updatePermission self.addPeer = addPeer @@ -48,6 +49,7 @@ private final class ChannelPermissionsControllerArguments { self.presentConversionToBroadcastGroup = presentConversionToBroadcastGroup self.openChannelExample = openChannelExample self.updateSlowmode = updateSlowmode + self.updateUnrestrictBoosters = updateUnrestrictBoosters self.toggleIsOptionExpanded = toggleIsOptionExpanded } } @@ -56,6 +58,7 @@ private enum ChannelPermissionsSection: Int32 { case permissions case slowmode case conversion + case unrestrictBoosters case kicked case exceptions } @@ -78,6 +81,9 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { case slowmodeHeader(PresentationTheme, String) case slowmode(PresentationTheme, PresentationStrings, Int32) case slowmodeInfo(PresentationTheme, String) + case unrestrictBoostersSwitch(PresentationTheme, String, Bool) + case unrestrictBoosters(PresentationTheme, PresentationStrings, Int32) + case unrestrictBoostersInfo(PresentationTheme, String) case conversionHeader(PresentationTheme, String) case conversion(PresentationTheme, String) case conversionInfo(PresentationTheme, String) @@ -94,6 +100,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return ChannelPermissionsSection.slowmode.rawValue case .conversionHeader, .conversion, .conversionInfo: return ChannelPermissionsSection.conversion.rawValue + case .unrestrictBoostersSwitch, .unrestrictBoosters, .unrestrictBoostersInfo: + return ChannelPermissionsSection.unrestrictBoosters.rawValue case .kicked: return ChannelPermissionsSection.kicked.rawValue case .exceptionsHeader, .add, .peerItem: @@ -113,18 +121,24 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return .index(999) case .conversionInfo: return .index(1000) - case .slowmodeHeader: + case .unrestrictBoostersSwitch: return .index(1001) - case .slowmode: + case .unrestrictBoosters: return .index(1002) - case .slowmodeInfo: + case .unrestrictBoostersInfo: return .index(1003) - case .kicked: + case .slowmodeHeader: return .index(1004) - case .exceptionsHeader: + case .slowmode: return .index(1005) - case .add: + case .slowmodeInfo: return .index(1006) + case .kicked: + return .index(1007) + case .exceptionsHeader: + return .index(1008) + case .add: + return .index(1009) case let .peerItem(_, _, _, _, _, participant, _, _, _, _): return .peer(participant.peer.id) } @@ -180,7 +194,25 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { } else { return false } - case let .kicked(lhsTheme, lhsText, lhsValue): + case let .unrestrictBoostersSwitch(lhsTheme, lhsTitle, lhsValue): + if case let .unrestrictBoostersSwitch(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .unrestrictBoosters(lhsTheme, lhsStrings, lhsValue): + if case let .unrestrictBoosters(rhsTheme, rhsStrings, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsValue == rhsValue { + return true + } else { + return false + } + case let .unrestrictBoostersInfo(lhsTheme, lhsValue): + if case let .unrestrictBoostersInfo(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue { + return true + } else { + return false + } + case let .kicked(lhsTheme, lhsText, lhsValue): if case let .kicked(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { @@ -325,6 +357,16 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .markdown(value), sectionId: self.section) { _ in arguments.openChannelExample() } + case let .unrestrictBoostersSwitch(_, title, value): + return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in + arguments.updateUnrestrictBoosters(value ? 1 : 0) + }) + case let .unrestrictBoosters(theme, strings, value): + return ChatUnrestrictBoostersItem(theme: theme, strings: strings, value: value, enabled: true, sectionId: self.section, updated: { value in + arguments.updateUnrestrictBoosters(value) + }) + case let .unrestrictBoostersInfo(_, value): + return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section) case let .kicked(_, text, value): return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openKicked() @@ -379,6 +421,7 @@ private struct ChannelPermissionsControllerState: Equatable { var searchingMembers: Bool = false var modifiedRightsFlags: TelegramChatBannedRightsFlags? var modifiedSlowmodeTimeout: Int32? + var modifiedUnrestrictBoosters: Int32? var expandedPermissions = Set() } @@ -564,6 +607,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen } entries.append(.permissionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SectionTitle)) + var rightIndex: Int = 0 for (rights, correspondingAdminRight) in allGroupPermissionList(peer: .channel(channel), expandMedia: false) { var enabled = true @@ -602,8 +646,26 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen entries.append(.conversionInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_BroadcastConvertInfo(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.groupingSeparator)).string)) } + let canSendText = !effectiveRightsFlags.contains(.banSendText) + let canSendMedia = banSendMediaSubList().allSatisfy({ !effectiveRightsFlags.contains($0.0) }) + + let slowModeTimeout = state.modifiedSlowmodeTimeout ?? (cachedData.slowModeTimeout ?? 0) + + if !canSendText || !canSendMedia || slowModeTimeout > 0 { + let unrestrictBoosters = state.modifiedUnrestrictBoosters ?? (cachedData.boostsToUnrestrict ?? 0) + let unrestrictEnabled = unrestrictBoosters > 0 + + entries.append(.unrestrictBoostersSwitch(presentationData.theme, presentationData.strings.GroupInfo_Permissions_DontRestrictBoosters, unrestrictEnabled)) + if unrestrictEnabled { + entries.append(.unrestrictBoosters(presentationData.theme, presentationData.strings, max(1, unrestrictBoosters))) + entries.append(.unrestrictBoostersInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_DontRestrictBoostersInfo)) + } else { + entries.append(.unrestrictBoostersInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_DontRestrictBoostersEnableInfo)) + } + } + entries.append(.slowmodeHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SlowmodeHeader)) - entries.append(.slowmode(presentationData.theme, presentationData.strings, state.modifiedSlowmodeTimeout ?? (cachedData.slowModeTimeout ?? 0))) + entries.append(.slowmode(presentationData.theme, presentationData.strings, slowModeTimeout)) entries.append(.slowmodeInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SlowmodeInfo)) entries.append(.kicked(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Removed, cachedData.participantsSummary.kickedCount.flatMap({ $0 == 0 ? "" : "\($0)" }) ?? "")) @@ -718,6 +780,9 @@ public func channelPermissionsController(context: AccountContext, updatedPresent let updateDefaultRightsDisposable = MetaDisposable() actionsDisposable.add(updateDefaultRightsDisposable) + let updateUnrestrictBoostersDisposable = MetaDisposable() + actionsDisposable.add(updateUnrestrictBoostersDisposable) + let peerView = Promise() peerView.set(sourcePeerId.get() |> mapToSignal(context.account.viewTracker.peerView)) @@ -1075,6 +1140,19 @@ public func channelPermissionsController(context: AccountContext, updatedPresent })) } }) + }, updateUnrestrictBoosters: { value in + updateState { state in + var state = state + state.modifiedUnrestrictBoosters = value + return state + } + + let _ = (peerView.get() + |> take(1) + |> deliverOnMainQueue).start(next: { view in + updateUnrestrictBoostersDisposable.set((context.engine.peers.updateChannelBoostsToUnlockRestrictions(peerId: view.peerId, boosts: value) + |> deliverOnMainQueue).start()) + }) }, toggleIsOptionExpanded: { flags in updateState { state in var state = state diff --git a/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift b/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift new file mode 100644 index 0000000000..320a91126b --- /dev/null +++ b/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift @@ -0,0 +1,342 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import TelegramUIPreferences +import TelegramPresentationData +import LegacyComponents +import ItemListUI +import PresentationDataUtils + +class ChatUnrestrictBoostersItem: ListViewItem, ItemListItem { + let theme: PresentationTheme + let strings: PresentationStrings + let value: Int32 + let sectionId: ItemListSectionId + let updated: (Int32) -> Void + + init(theme: PresentationTheme, strings: PresentationStrings, value: Int32, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { + self.theme = theme + self.strings = strings + self.value = value + self.sectionId = sectionId + self.updated = updated + } + + 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 = ChatUnrestrictBoostersItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + 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 { + if let nodeValue = node() as? ChatUnrestrictBoostersItemNode { + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } + } +} + +private let allowedValues: [Int32] = [1, 2, 3, 4, 5] + +class ChatUnrestrictBoostersItemNode: ListViewItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let maskNode: ASImageNode + + private let iconNodes: [ASImageNode] + private let textNodes: [TextNode] + private var sliderView: TGPhotoEditorSliderView? + + private var item: ChatUnrestrictBoostersItem? + private var layoutParams: ListViewItemLayoutParams? + private var reportedValue: Int32? + + init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.maskNode = ASImageNode() + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.iconNodes = allowedValues.map { _ -> ASImageNode in + let iconNode = ASImageNode() + iconNode.isUserInteractionEnabled = false + iconNode.displaysAsynchronously = false + return iconNode + } + + self.textNodes = allowedValues.map { _ -> TextNode in + let textNode = TextNode() + textNode.isUserInteractionEnabled = false + textNode.displaysAsynchronously = false + return textNode + } + + super.init(layerBacked: false, dynamicBounce: false) + + self.iconNodes.forEach(self.addSubnode) + self.textNodes.forEach(self.addSubnode) + } + + func forceSetValue(_ value: Int32) { + if let sliderView = self.sliderView { + sliderView.value = CGFloat(value) + } + } + + func updateSliderView() { + if let sliderView = self.sliderView, let item = self.item { + sliderView.maximumValue = CGFloat(allowedValues.count - 1) + sliderView.positionsCount = allowedValues.count + var value: Int32 = 0 + for i in 0 ..< allowedValues.count { + if allowedValues[i] >= item.value { + value = Int32(i) + break + } + } + + sliderView.value = CGFloat(value) + + sliderView.isUserInteractionEnabled = true + sliderView.alpha = 1.0 + sliderView.layer.allowsGroupOpacity = false + } + } + + override func didLoad() { + super.didLoad() + + self.view.disablesInteractiveTransitionGestureRecognizer = true + + let sliderView = TGPhotoEditorSliderView() + sliderView.limitValueChangedToLatestState = true + sliderView.enablePanHandling = true + sliderView.trackCornerRadius = 2.0 + sliderView.lineSize = 4.0 + sliderView.dotSize = 5.0 + sliderView.minimumValue = 0.0 + sliderView.maximumValue = CGFloat(allowedValues.count - 1) + sliderView.positionsCount = allowedValues.count + sliderView.startValue = 0.0 + sliderView.disablesInteractiveTransitionGestureRecognizer = true + sliderView.useLinesForPositions = true + if let item = self.item, let params = self.layoutParams { + var value: Int32 = 0 + for i in 0 ..< allowedValues.count { + if allowedValues[i] >= item.value { + value = Int32(i) + break + } + } + + sliderView.value = CGFloat(value) + self.reportedValue = item.value + sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor + sliderView.backColor = item.theme.list.itemSwitchColors.frameColor + sliderView.startColor = item.theme.list.itemSwitchColors.frameColor + sliderView.trackColor = item.theme.list.itemAccentColor + sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) + + sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) + sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) + } + self.view.addSubview(sliderView) + sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) + self.sliderView = sliderView + + self.updateSliderView() + } + + func asyncLayout() -> (_ item: ChatUnrestrictBoostersItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let currentItem = self.item + let makeTextLayouts = self.textNodes.map(TextNode.asyncLayout) + + return { item, params, neighbors in + var themeUpdated = false + if currentItem?.theme !== item.theme { + themeUpdated = true + } + + let contentSize: CGSize + let insets: UIEdgeInsets + let separatorHeight = UIScreenPixel + + var textLayoutAndApply: [(TextNodeLayout, () -> TextNode)] = [] + + for i in 0 ..< allowedValues.count { + let value = allowedValues[i] + + let valueString: String + if value == 0 { + valueString = item.strings.GroupInfo_Permissions_SlowmodeValue_Off + } else { + valueString = "\(value)" //shortTimeIntervalString(strings: item.strings, value: value) + } + let (textLayout, textApply) = makeTextLayouts[i](TextNodeLayoutArguments(attributedString: NSAttributedString(string: valueString, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) + textLayoutAndApply.append((textLayout, textApply)) + } + + contentSize = CGSize(width: params.width, height: 88.0) + insets = itemListNeighborsGroupedInsets(neighbors, params) + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + let layoutSize = layout.size + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + strongSelf.layoutParams = params + + strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor + 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 strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners + } + let bottomStripeInset: CGFloat + let bottomStripeOffset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = 0.0 + bottomStripeOffset = -separatorHeight + strongSelf.bottomStripeNode.isHidden = false + default: + bottomStripeInset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeOffset = 0.0 + } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + + strongSelf.backgroundNode.frame = 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.maskNode.frame = strongSelf.backgroundNode.frame.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)) + + for (_, apply) in textLayoutAndApply { + let _ = apply() + } + + let textNodes: [(TextNode, CGSize)] = textLayoutAndApply.map { layout, apply -> (TextNode, CGSize) in + let node = apply() + return (node, layout.size) + } + + let delta = (params.width - params.leftInset - params.rightInset - 18.0 * 2.0) / CGFloat(textNodes.count - 1) + for i in 0 ..< textNodes.count { + let iconNode = strongSelf.iconNodes[i] + let (textNode, textSize) = textNodes[i] + + if themeUpdated { + iconNode.image = generateTintedImage(image: i == 0 ? UIImage(bundleImageName: "Chat/Message/Boost") : UIImage(bundleImageName: "Chat/Message/Boosts"), color: item.theme.list.itemSecondaryTextColor) + } + + var position = params.leftInset + 18.0 + delta * CGFloat(i) + var iconOffset: CGFloat = 9.0 + let textOffset: CGFloat = 5.0 + if i == textNodes.count - 1 { + position -= textSize.width + 8.0 + } else if i > 0 { + position -= textSize.width / 2.0 + } else if i == 0 { + position += textSize.width + iconOffset = 6.0 + } + + if let icon = iconNode.image { + iconNode.frame = CGRect(origin: CGPoint(x: position - iconOffset, y: 16.0), size: icon.size) + textNode.frame = CGRect(origin: CGPoint(x: position + textOffset, y: 15.0), size: textSize) + } + } + + if let sliderView = strongSelf.sliderView { + if themeUpdated { + sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor + sliderView.backColor = item.theme.list.itemSwitchColors.frameColor + sliderView.startColor = item.theme.list.itemSwitchColors.frameColor + sliderView.trackColor = item.theme.list.itemAccentColor + sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) + } + + sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) + sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) + + strongSelf.updateSliderView() + } + } + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + + @objc func sliderValueChanged() { + guard let item = self.item, let sliderView = self.sliderView else { + return + } + + let position = Int(sliderView.value) + let value: Int32 = allowedValues[max(0, min(allowedValues.count - 1, position))] + if self.reportedValue != value { + self.reportedValue = value + item.updated(value) + } + } +} diff --git a/submodules/Postbox/Sources/PeerChatInterfaceState.swift b/submodules/Postbox/Sources/PeerChatInterfaceState.swift index 3e491225f0..c32d435d84 100644 --- a/submodules/Postbox/Sources/PeerChatInterfaceState.swift +++ b/submodules/Postbox/Sources/PeerChatInterfaceState.swift @@ -13,7 +13,7 @@ public final class StoredPeerChatInterfaceState: Codable, Equatable { case associatedMessageIds case data } - + public let overrideChatTimestamp: Int32? public let historyScrollMessageIndex: MessageIndex? public let associatedMessageIds: [MessageId] @@ -65,11 +65,3 @@ public final class StoredPeerChatInterfaceState: Codable, Equatable { return true } } - -/*public protocol PeerChatInterfaceState: PostboxCoding { - var chatListEmbeddedState: PeerChatListEmbeddedInterfaceState? { get } - var historyScrollMessageIndex: MessageIndex? { get } - var associatedMessageIds: [MessageId] { get } - - func isEqual(to: PeerChatInterfaceState) -> Bool -}*/ diff --git a/submodules/PremiumUI/Resources/boost.scn b/submodules/PremiumUI/Resources/boost.scn new file mode 100644 index 0000000000..d8f41739bf Binary files /dev/null and b/submodules/PremiumUI/Resources/boost.scn differ diff --git a/submodules/PremiumUI/Sources/BadgeLabelView.swift b/submodules/PremiumUI/Sources/BadgeLabelView.swift index af32cf3bce..315551c5f0 100644 --- a/submodules/PremiumUI/Sources/BadgeLabelView.swift +++ b/submodules/PremiumUI/Sources/BadgeLabelView.swift @@ -14,6 +14,14 @@ final class BadgeLabelView: UIView { var currentValue: Int32 = 0 + var color: UIColor = .white { + didSet { + for view in self.labels { + view.textColor = self.color + } + } + } + init() { super.init(frame: CGRect(origin: .zero, size: labelSize)) @@ -25,7 +33,7 @@ final class BadgeLabelView: UIView { } else { label.text = "\(i)" } - label.textColor = .white + label.textColor = self.color label.font = font label.textAlignment = .center label.frame = CGRect(x: 0, y: height, width: labelWidth, height: labelHeight) @@ -81,6 +89,15 @@ final class BadgeLabelView: UIView { fatalError("init(coder:) has not been implemented") } + var color: UIColor = .white { + didSet { + self.staticLabel.textColor = self.color + for (_, view) in self.itemViews { + view.color = self.color + } + } + } + func update(value: String, transition: Transition) -> CGSize { if value.contains(" ") { for (_, view) in self.itemViews { @@ -88,7 +105,7 @@ final class BadgeLabelView: UIView { } if self.staticLabel.superview == nil { - self.staticLabel.textColor = .white + self.staticLabel.textColor = self.color self.staticLabel.font = font self.addSubview(self.staticLabel) diff --git a/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift b/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift new file mode 100644 index 0000000000..64e5a28de4 --- /dev/null +++ b/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift @@ -0,0 +1,193 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import SceneKit +import GZip +import AppBundle +import LegacyComponents +import AvatarNode +import TelegramCore +import MultilineTextComponent +import TelegramPresentationData + +private let sceneVersion: Int = 3 + +public final class BoostHeaderBackgroundComponent: Component { + let isVisible: Bool + let hasIdleAnimations: Bool + + public init(isVisible: Bool, hasIdleAnimations: Bool) { + self.isVisible = isVisible + self.hasIdleAnimations = hasIdleAnimations + } + + public static func ==(lhs: BoostHeaderBackgroundComponent, rhs: BoostHeaderBackgroundComponent) -> Bool { + return lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations + } + + public final class View: UIView, SCNSceneRendererDelegate { + private var _ready = Promise() + var ready: Signal { + return self._ready.get() + } + + private let sceneView: SCNView + + private var previousInteractionTimestamp: Double = 0.0 + private var hasIdleAnimations = false + + override init(frame: CGRect) { + self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0))) + self.sceneView.backgroundColor = .clear + self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) + self.sceneView.isUserInteractionEnabled = false + self.sceneView.preferredFramesPerSecond = 60 + + super.init(frame: frame) + + self.addSubview(self.sceneView) + + self.setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + private func setup() { + guard let url = getAppBundle().url(forResource: "boost", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else { + return + } + + self.sceneView.scene = scene + self.sceneView.delegate = self + + let _ = self.sceneView.snapshot() + } + + private var didSetReady = false + public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { + if !self.didSetReady { + self.didSetReady = true + + Queue.mainQueue().justDispatch { + self._ready.set(.single(true)) + self.onReady() + } + } + } + + private func onReady() { + self.playAppearanceAnimation(explode: true) + } + + + private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) { + guard let scene = self.sceneView.scene else { + return + } + + let currentTime = CACurrentMediaTime() + self.previousInteractionTimestamp = currentTime + + if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false), let particlesBottomLeft = scene.rootNode.childNode(withName: "particles_left_bottom", recursively: false), let particlesBottomRight = scene.rootNode.childNode(withName: "particles_right_bottom", recursively: false) { + if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first, let leftBottomParticleSystem = particlesBottomLeft.particleSystems?.first, let rightBottomParticleSystem = particlesBottomRight.particleSystems?.first { + leftParticleSystem.speedFactor = 2.0 + leftParticleSystem.particleVelocity = 1.6 + leftParticleSystem.birthRate = 60.0 + leftParticleSystem.particleLifeSpan = 4.0 + + rightParticleSystem.speedFactor = 2.0 + rightParticleSystem.particleVelocity = 1.6 + rightParticleSystem.birthRate = 60.0 + rightParticleSystem.particleLifeSpan = 4.0 + +// leftBottomParticleSystem.speedFactor = 2.0 + leftBottomParticleSystem.particleVelocity = 1.6 + leftBottomParticleSystem.birthRate = 24.0 + leftBottomParticleSystem.particleLifeSpan = 7.0 + +// rightBottomParticleSystem.speedFactor = 2.0 + rightBottomParticleSystem.particleVelocity = 1.6 + rightBottomParticleSystem.birthRate = 24.0 + rightBottomParticleSystem.particleLifeSpan = 7.0 + + node.physicsField?.isActive = true + Queue.mainQueue().after(1.0) { + node.physicsField?.isActive = false + + leftParticleSystem.birthRate = 12.0 + leftParticleSystem.particleVelocity = 1.2 + leftParticleSystem.particleLifeSpan = 3.0 + + rightParticleSystem.birthRate = 12.0 + rightParticleSystem.particleVelocity = 1.2 + rightParticleSystem.particleLifeSpan = 3.0 + + leftBottomParticleSystem.particleVelocity = 1.2 + leftBottomParticleSystem.birthRate = 7.0 + leftBottomParticleSystem.particleLifeSpan = 5.0 + + rightBottomParticleSystem.particleVelocity = 1.2 + rightBottomParticleSystem.birthRate = 7.0 + rightBottomParticleSystem.particleLifeSpan = 5.0 + + let leftAnimation = POPBasicAnimation() + leftAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in + property?.readBlock = { particleSystem, values in + values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor + } + property?.writeBlock = { particleSystem, values in + (particleSystem as! SCNParticleSystem).speedFactor = values!.pointee + } + property?.threshold = 0.01 + }) as! POPAnimatableProperty) + leftAnimation.fromValue = 1.2 as NSNumber + leftAnimation.toValue = 0.85 as NSNumber + leftAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + leftAnimation.duration = 0.5 + leftParticleSystem.pop_add(leftAnimation, forKey: "speedFactor") + + let rightAnimation = POPBasicAnimation() + rightAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in + property?.readBlock = { particleSystem, values in + values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor + } + property?.writeBlock = { particleSystem, values in + (particleSystem as! SCNParticleSystem).speedFactor = values!.pointee + } + property?.threshold = 0.01 + }) as! POPAnimatableProperty) + rightAnimation.fromValue = 1.2 as NSNumber + rightAnimation.toValue = 0.85 as NSNumber + rightAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + rightAnimation.duration = 0.5 + rightParticleSystem.pop_add(rightAnimation, forKey: "speedFactor") + } + } + } + } + + func update(component: BoostHeaderBackgroundComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height)) + if self.sceneView.superview == self { + self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) + } + + self.hasIdleAnimations = component.hasIdleAnimations + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index e4d93d125e..d3e9787af2 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -432,7 +432,11 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { case let .channelsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .channel(_, _, peer, boosts, isRevealed): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: { + var isGroup = false + if case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(isGroup ? presentationData.strings.BoostGift_GroupBoosts($0) : presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: { }, setPeerIdWithRevealedOptions: { lhs, rhs in arguments.setItemIdWithRevealedOptions(lhs, rhs) }, removePeer: { id in @@ -634,6 +638,11 @@ private func createGiveawayControllerEntries( minDate: Int32, maxDate: Int32 ) -> [CreateGiveawayEntry] { + var isGroup = false + if let peer = peers[peerId], case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + var entries: [CreateGiveawayEntry] = [] switch subject { @@ -719,7 +728,7 @@ private func createGiveawayControllerEntries( entries.append(.subscriptionsInfo(presentationData.theme, presentationData.strings.BoostGift_QuantityInfo)) } - entries.append(.channelsHeader(presentationData.theme, presentationData.strings.BoostGift_ChannelsTitle.uppercased())) + entries.append(.channelsHeader(presentationData.theme, isGroup ? presentationData.strings.BoostGift_GroupsAndChannelsTitle.uppercased() : presentationData.strings.BoostGift_ChannelsAndGroupsTitle.uppercased())) var index: Int32 = 0 let channels = [peerId] + state.channels for channelId in channels { @@ -728,8 +737,8 @@ private func createGiveawayControllerEntries( } index += 1 } - entries.append(.channelAdd(presentationData.theme, presentationData.strings.BoostGift_AddChannel)) - entries.append(.channelsInfo(presentationData.theme, presentationData.strings.BoostGift_ChannelsInfo)) + entries.append(.channelAdd(presentationData.theme, isGroup ? presentationData.strings.BoostGift_AddGroupOrChannel : presentationData.strings.BoostGift_AddChannelOrGroup)) + entries.append(.channelsInfo(presentationData.theme, isGroup ? presentationData.strings.BoostGift_GroupsAndChannelsInfo : presentationData.strings.BoostGift_ChannelsAndGroupsInfo)) entries.append(.usersHeader(presentationData.theme, presentationData.strings.BoostGift_UsersTitle.uppercased())) @@ -752,9 +761,9 @@ private func createGiveawayControllerEntries( countriesText = presentationData.strings.BoostGift_FromAllCountries } - entries.append(.usersAll(presentationData.theme, presentationData.strings.BoostGift_AllSubscribers, countriesText, !state.onlyNewEligible)) - entries.append(.usersNew(presentationData.theme, presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible)) - entries.append(.usersInfo(presentationData.theme, presentationData.strings.BoostGift_LimitSubscribersInfo)) + entries.append(.usersAll(presentationData.theme, isGroup ? presentationData.strings.BoostGift_Group_AllMembers : presentationData.strings.BoostGift_AllSubscribers, countriesText, !state.onlyNewEligible)) + entries.append(.usersNew(presentationData.theme, isGroup ? presentationData.strings.BoostGift_Group_OnlyNewMembers : presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible)) + entries.append(.usersInfo(presentationData.theme, isGroup ? presentationData.strings.BoostGift_Group_LimitMembersInfo : presentationData.strings.BoostGift_LimitSubscribersInfo)) if case .generic = subject { appendDurationEntries() @@ -782,7 +791,12 @@ private func createGiveawayControllerEntries( entries.append(.timeHeader(presentationData.theme, presentationData.strings.BoostGift_DateTitle.uppercased())) entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate, state.pickingExpiryDate, state.pickingExpiryTime)) - entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string)) + + if isGroup { + entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_Group_DateInfo(presentationData.strings.BoostGift_Group_DateInfoMembers(Int32(state.subscriptions))).string)) + } else { + entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string)) + } case .gift: appendDurationEntries() } @@ -948,8 +962,13 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio presentationData = presentationData.withUpdated(theme: updatedTheme) let (state, peersMap) = stateAndPeersMap + + var isGroup = false + if let peer = peersMap[peerId], case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } - let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.BoostGift_Title, text: presentationData.strings.BoostGift_Description, cancel: { + let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.BoostGift_Title, text: isGroup ? presentationData.strings.BoostGift_Group_Description : presentationData.strings.BoostGift_Description, cancel: { dismissImpl?() }) diff --git a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift b/submodules/PremiumUI/Sources/GiftAvatarComponent.swift index e3fd75b997..6a1b879649 100644 --- a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift +++ b/submodules/PremiumUI/Sources/GiftAvatarComponent.swift @@ -16,7 +16,7 @@ import TelegramPresentationData private let sceneVersion: Int = 3 -class GiftAvatarComponent: Component { +final class GiftAvatarComponent: Component { let context: AccountContext let theme: PresentationTheme let peers: [EnginePeer] diff --git a/submodules/PremiumUI/Sources/GiveawayInfoController.swift b/submodules/PremiumUI/Sources/GiveawayInfoController.swift index fc605e2462..0fdedc54e2 100644 --- a/submodules/PremiumUI/Sources/GiveawayInfoController.swift +++ b/submodules/PremiumUI/Sources/GiveawayInfoController.swift @@ -111,6 +111,8 @@ public func presentGiveawayInfoController( additionalPrizes = "\n\n" + presentationData.strings.Chat_Giveaway_Info_AdditionalPrizes(peerName, "\(quantity) \(prizeDescription)").string } + let isGroup = "".isEmpty + switch giveawayInfo { case let .ongoing(start, status): let startDate = presentationData.strings.Chat_Giveaway_Info_FullDate( @@ -122,9 +124,17 @@ public func presentGiveawayInfoController( let intro: String if case .almostOver = status { - intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + if isGroup { + intro = presentationData.strings.Chat_Giveaway_Info_Group_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } else { + intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } } else { - intro = presentationData.strings.Chat_Giveaway_Info_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + if isGroup { + intro = presentationData.strings.Chat_Giveaway_Info_Group_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } else { + intro = presentationData.strings.Chat_Giveaway_Info_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } } let ending: String @@ -136,7 +146,7 @@ public func presentGiveawayInfoController( ending = presentationData.strings.Chat_Giveaway_Info_OngoingNew(untilDate, randomUsers, peerName, startDate).string } } else { - let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity) + let randomSubscribers = isGroup ? presentationData.strings.Chat_Giveaway_Info_Group_RandomMembers(quantity) : presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity) if channelsCount > 1 { ending = presentationData.strings.Chat_Giveaway_Info_OngoingMany(untilDate, randomSubscribers, peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(channelsCount - 1))).string } else { @@ -190,7 +200,12 @@ public func presentGiveawayInfoController( let finishDate = stringForDate(timestamp: finish, timeZone: timeZone, strings: presentationData.strings) title = presentationData.strings.Chat_Giveaway_Info_EndedTitle - let intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + let intro: String + if isGroup { + intro = presentationData.strings.Chat_Giveaway_Info_Group_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } else { + intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string + } var ending: String if onlyNewSubscribers { @@ -201,7 +216,7 @@ public func presentGiveawayInfoController( ending = presentationData.strings.Chat_Giveaway_Info_EndedNew(finishDate, randomUsers, peerName, startDate).string } } else { - let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity) + let randomSubscribers = isGroup ? presentationData.strings.Chat_Giveaway_Info_Group_RandomMembers(quantity) : presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity) if channelsCount > 1 { ending = presentationData.strings.Chat_Giveaway_Info_EndedMany(finishDate, randomSubscribers, peerName).string } else { diff --git a/submodules/PremiumUI/Sources/PageIndicatorComponent.swift b/submodules/PremiumUI/Sources/PageIndicatorComponent.swift index e8ea2d7380..1e5497a529 100644 --- a/submodules/PremiumUI/Sources/PageIndicatorComponent.swift +++ b/submodules/PremiumUI/Sources/PageIndicatorComponent.swift @@ -55,10 +55,11 @@ public final class PageIndicatorComponent: Component { } public func update(component: PageIndicatorComponent, availableSize: CGSize, transition: Transition) -> CGSize { + let isFirstTime = self.component == nil self.component = component self.indicatorView.pageCount = component.pageCount - self.indicatorView.setProgress(progress: component.position) + self.indicatorView.setProgress(progress: component.position, animated: !isFirstTime) self.indicatorView.activeColor = component.activeColor self.indicatorView.inactiveColor = component.inactiveColor @@ -155,9 +156,9 @@ private final class PageIndicatorView: UIView { return CGSize(width: self.itemSize * CGFloat(self.displayCount), height: self.itemSize) } - public func setProgress(progress: CGFloat) { + public func setProgress(progress: CGFloat, animated: Bool = true) { let currentPage = Int(round(progress * CGFloat(self.pageCount - 1))) - self.setCurrentPage(at: currentPage, animated: true) + self.setCurrentPage(at: currentPage, animated: animated) } public func updateViewSize() { @@ -186,8 +187,7 @@ private final class PageIndicatorView: UIView { if currentPage < self.displayCount { self.items = (-2..<(self.displayCount + 2)) .map { ItemView(itemSize: self.itemSize, dotSize: self.dotSize, smallDotSizeRatio: self.smallDotSizeRatio, mediumDotSizeRatio: self.mediumDotSizeRatio, index: $0) } - } - else { + } else { guard let firstItem = self.items.first else { return } guard let lastItem = self.items.last else { return } self.items = (firstItem.index...lastItem.index) @@ -231,21 +231,32 @@ private final class PageIndicatorView: UIView { private func updateDotPosition(currentPage: Int, animated: Bool) { let duration = animated ? self.animationDuration : 0 - if currentPage == 0 { - let x = -self.scrollView.contentInset.left - self.moveScrollView(x: x, duration: duration) + let action: (Int, Double) -> Void = { currentPage, duration in + if currentPage == 0 { + let x = -self.scrollView.contentInset.left + self.moveScrollView(x: x, duration: duration) + } else if currentPage == self.pageCount - 1 { + let x = self.scrollView.contentSize.width - self.scrollView.bounds.width + self.scrollView.contentInset.right + self.moveScrollView(x: x, duration: duration) + } else if CGFloat(currentPage) * self.itemSize <= self.scrollView.contentOffset.x + self.itemSize { + let x = self.scrollView.contentOffset.x - self.itemSize + self.moveScrollView(x: x, duration: duration) + } else if CGFloat(currentPage) * self.itemSize + self.itemSize >= self.scrollView.contentOffset.x + self.scrollView.bounds.width - self.itemSize { + let x = self.scrollView.contentOffset.x + self.itemSize + self.moveScrollView(x: x, duration: duration) + } } - else if currentPage == self.pageCount - 1 { - let x = self.scrollView.contentSize.width - self.scrollView.bounds.width + self.scrollView.contentInset.right - self.moveScrollView(x: x, duration: duration) - } - else if CGFloat(currentPage) * self.itemSize <= self.scrollView.contentOffset.x + self.itemSize { - let x = self.scrollView.contentOffset.x - self.itemSize - self.moveScrollView(x: x, duration: duration) - } - else if CGFloat(currentPage) * self.itemSize + self.itemSize >= self.scrollView.contentOffset.x + self.scrollView.bounds.width - self.itemSize { - let x = self.scrollView.contentOffset.x + self.itemSize - self.moveScrollView(x: x, duration: duration) + + if !animated { + if currentPage == 0 { + action(currentPage, 0) + } else { + for i in 0 ..< currentPage { + action(i, 0) + } + } + } else { + action(currentPage, duration) } } @@ -274,12 +285,16 @@ private final class PageIndicatorView: UIView { } } - private func moveScrollView(x: CGFloat, duration: TimeInterval) { + private func moveScrollView(x: CGFloat, duration: TimeInterval = 0.0) { let direction = self.behaviorDirection(x: x) self.reusedView(direction: direction) - UIView.animate(withDuration: duration, animations: { [unowned self] in + if duration > 0.0 { + UIView.animate(withDuration: duration, animations: { [unowned self] in + self.scrollView.contentOffset.x = x + }) + } else { self.scrollView.contentOffset.x = x - }) + } } private enum Direction { diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index ef521840b1..d740f64aa0 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -19,31 +19,41 @@ import TextFormat import SolidRoundedButtonComponent import BlurredBackgroundComponent import UndoUI +import ConfettiEffect -func requiredBoostSubjectLevel(subject: BoostSubject, context: AccountContext, configuration: PremiumConfiguration) -> Int32 { +func requiredBoostSubjectLevel(subject: BoostSubject, group: Bool, context: AccountContext, configuration: PremiumConfiguration) -> Int32 { switch subject { case .stories: return 1 case let .channelReactions(reactionCount): return reactionCount case let .nameColors(colors): - if let value = context.peerNameColors.nameColorsChannelMinRequiredBoostLevel[colors.rawValue] { - return value + if group { + if let value = context.peerNameColors.nameColorsGroupMinRequiredBoostLevel[colors.rawValue] { + return value + } } else { - return 1 + if let value = context.peerNameColors.nameColorsChannelMinRequiredBoostLevel[colors.rawValue] { + return value + } } + return 1 case .nameIcon: return configuration.minChannelNameIconLevel case .profileColors: return configuration.minChannelProfileColorLevel case .profileIcon: - return configuration.minChannelProfileIconLevel + return group ? configuration.minGroupProfileIconLevel : configuration.minChannelProfileIconLevel case .emojiStatus: - return configuration.minChannelEmojiStatusLevel + return group ? configuration.minGroupEmojiStatusLevel : configuration.minChannelEmojiStatusLevel case .wallpaper: - return configuration.minChannelWallpaperLevel + return group ? configuration.minGroupWallpaperLevel : configuration.minChannelWallpaperLevel case .customWallpaper: - return configuration.minChannelCustomWallpaperLevel + return group ? configuration.minGroupCustomWallpaperLevel : configuration.minChannelCustomWallpaperLevel + case .audioTranscription: + return configuration.minGroupAudioTranscriptionLevel + case .emojiPack: + return configuration.minGroupEmojiPackLevel } } @@ -57,9 +67,11 @@ public enum BoostSubject: Equatable { case emojiStatus case wallpaper case customWallpaper + case audioTranscription + case emojiPack - public func requiredLevel(context: AccountContext, configuration: PremiumConfiguration) -> Int32 { - return requiredBoostSubjectLevel(subject: self, context: context, configuration: configuration) + public func requiredLevel(group: Bool, context: AccountContext, configuration: PremiumConfiguration) -> Int32 { + return requiredBoostSubjectLevel(subject: self, group: group, context: context, configuration: configuration) } } @@ -232,8 +244,10 @@ private final class LevelSectionComponent: CombinedComponent { case emojiStatus case wallpaper(Int32) case customWallpaper + case audioTranscription + case emojiPack - func title(strings: PresentationStrings) -> String { + func title(strings: PresentationStrings, isGroup: Bool) -> String { switch self { case let .story(value): return strings.ChannelBoost_Table_StoriesPerDay(value) @@ -242,9 +256,9 @@ private final class LevelSectionComponent: CombinedComponent { case let .nameColor(value): return strings.ChannelBoost_Table_NameColor(value) case let .profileColor(value): - return strings.ChannelBoost_Table_ProfileColor(value) + return isGroup ? strings.ChannelBoost_Table_Group_ProfileColor(value) : strings.ChannelBoost_Table_ProfileColor(value) case .profileIcon: - return strings.ChannelBoost_Table_ProfileLogo + return isGroup ? strings.ChannelBoost_Table_Group_ProfileLogo : strings.ChannelBoost_Table_ProfileLogo case let .linkColor(value): return strings.ChannelBoost_Table_StyleForHeaders(value) case .linkIcon: @@ -252,9 +266,13 @@ private final class LevelSectionComponent: CombinedComponent { case .emojiStatus: return strings.ChannelBoost_Table_EmojiStatus case let .wallpaper(value): - return strings.ChannelBoost_Table_Wallpaper(value) + return isGroup ? strings.ChannelBoost_Table_Group_Wallpaper(value) : strings.ChannelBoost_Table_Wallpaper(value) case .customWallpaper: - return strings.ChannelBoost_Table_CustomWallpaper + return isGroup ? strings.ChannelBoost_Table_Group_CustomWallpaper : strings.ChannelBoost_Table_CustomWallpaper + case .audioTranscription: + return "Voice-to-Text Conversion" + case .emojiPack: + return "Custom Emojipack" } } @@ -280,6 +298,10 @@ private final class LevelSectionComponent: CombinedComponent { return "Premium/BoostPerk/Wallpaper" case .customWallpaper: return "Premium/BoostPerk/CustomWallpaper" + case .audioTranscription: + return "Premium/BoostPerk/AudioTranscription" + case .emojiPack: + return "Premium/BoostPerk/EmojiPack" } } } @@ -289,19 +311,22 @@ private final class LevelSectionComponent: CombinedComponent { let level: Int32 let isFirst: Bool let perks: [Perk] + let isGroup: Bool init( theme: PresentationTheme, strings: PresentationStrings, level: Int32, isFirst: Bool, - perks: [Perk] + perks: [Perk], + isGroup: Bool ) { self.theme = theme self.strings = strings self.level = level self.isFirst = isFirst self.perks = perks + self.isGroup = isGroup } static func ==(lhs: LevelSectionComponent, rhs: LevelSectionComponent) -> Bool { @@ -317,6 +342,9 @@ private final class LevelSectionComponent: CombinedComponent { if lhs.perks != rhs.perks { return false } + if lhs.isGroup != rhs.isGroup { + return false + } return true } @@ -341,7 +369,7 @@ private final class LevelSectionComponent: CombinedComponent { LevelPerkComponent( theme: component.theme, iconName: value.iconName, - text: value.title(strings: component.strings) + text: value.title(strings: component.strings, isGroup: component.isGroup) ) ) ) @@ -355,13 +383,12 @@ private final class LevelSectionComponent: CombinedComponent { context.add(list .position(CGPoint(x: context.availableSize.width / 2.0, y: header.size.height + list.size.height / 2.0))) - let height = header.size.height + list.size.height - return CGSize(width: context.availableSize.width, height: height) + return CGSize(width: context.availableSize.width, height: header.size.height + list.size.height) } } } -private final class LimitSheetContent: CombinedComponent { +private final class SheetContent: CombinedComponent { typealias EnvironmentType = (Empty, ScrollChildEnvironment) let context: AccountContext @@ -369,41 +396,50 @@ private final class LimitSheetContent: CombinedComponent { let strings: PresentationStrings let insets: UIEdgeInsets - let peer: EnginePeer - let subject: BoostSubject - let status: ChannelBoostStatus + let peerId: EnginePeer.Id + let mode: PremiumBoostLevelsScreen.Mode + let status: ChannelBoostStatus? + let boostState: InternalBoostState.DisplayData? + let boost: () -> Void let copyLink: (String) -> Void let dismiss: () -> Void let openStats: (() -> Void)? let openGift: (() -> Void)? + let openPeer: ((EnginePeer) -> Void)? init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, insets: UIEdgeInsets, - peer: EnginePeer, - subject: BoostSubject, - status: ChannelBoostStatus, + peerId: EnginePeer.Id, + mode: PremiumBoostLevelsScreen.Mode, + status: ChannelBoostStatus?, + boostState: InternalBoostState.DisplayData?, + boost: @escaping () -> Void, copyLink: @escaping (String) -> Void, dismiss: @escaping () -> Void, openStats: (() -> Void)?, - openGift: (() -> Void)? + openGift: (() -> Void)?, + openPeer: ((EnginePeer) -> Void)? ) { self.context = context self.theme = theme self.strings = strings self.insets = insets - self.peer = peer - self.subject = subject + self.peerId = peerId + self.mode = mode self.status = status + self.boostState = boostState + self.boost = boost self.copyLink = copyLink self.dismiss = dismiss self.openStats = openStats self.openGift = openGift + self.openPeer = openPeer } - static func ==(lhs: LimitSheetContent, rhs: LimitSheetContent) -> Bool { + static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { if lhs.context !== rhs.context { return false } @@ -413,31 +449,81 @@ private final class LimitSheetContent: CombinedComponent { if lhs.insets != rhs.insets { return false } - if lhs.peer != rhs.peer { + if lhs.peerId != rhs.peerId { return false } - if lhs.subject != rhs.subject { + if lhs.mode != rhs.mode { return false } if lhs.status != rhs.status { return false } + if lhs.boostState != rhs.boostState { + return false + } return true } final class State: ComponentState { var cachedChevronImage: (UIImage, PresentationTheme)? + var cachedIconImage: UIImage? + + private(set) var peer: EnginePeer? + private(set) var memberPeer: EnginePeer? + + private var disposable: Disposable? + private var memberDisposable: Disposable? + + init(context: AccountContext, peerId: EnginePeer.Id, userId: EnginePeer.Id?) { + super.init() + + self.disposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).startStrict(next: { [weak self] peer in + guard let self else { + return + } + self.peer = peer + self.updated() + }) + + if let userId { + self.memberDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: userId)) + |> deliverOnMainQueue).startStrict(next: { [weak self] peer in + guard let self else { + return + } + self.memberPeer = peer + self.updated() + }) + } + } + + deinit { + self.disposable?.dispose() + self.memberDisposable?.dispose() + } } func makeState() -> State { - return State() + var userId: EnginePeer.Id? + if case let .user(mode) = mode, case let .groupPeer(peerId, _) = mode { + userId = peerId + } + return State(context: self.context, peerId: self.peerId, userId: userId) } static var body: Body { + let iconBackground = Child(Image.self) + let icon = Child(BundleIconComponent.self) + //let icon = Child(LottieComponent.self) + + let peerShortcut = Child(Button.self) let text = Child(BalancedTextComponent.self) + let alternateText = Child(List.self) let limit = Child(PremiumLimitDisplayComponent.self) let linkButton = Child(SolidRoundedButtonComponent.self) - let button = Child(SolidRoundedButtonComponent.self) + let boostButton = Child(SolidRoundedButtonComponent.self) + let copyButton = Child(SolidRoundedButtonComponent.self) let orLeftLine = Child(Rectangle.self) let orRightLine = Child(Rectangle.self) @@ -458,71 +544,151 @@ private final class LimitSheetContent: CombinedComponent { let textSideInset: CGFloat = 32.0 // + environment.safeInsets.left let iconName = "Premium/Boost" - let badgeText = "\(component.status.boosts)" + let peerName = state.peer?.compactDisplayTitle ?? "" - var remaining: Int? - if let nextLevelBoosts = component.status.nextLevelBoosts { - remaining = nextLevelBoosts - component.status.boosts + var isGroup = false + if let peer = state.peer, case let .channel(channel) = peer, case .group = channel.info { + isGroup = true } - var textString = "" - let actionButtonText = strings.ChannelBoost_CopyLink - let buttonIconName = "Premium/CopyLink" - - if let remaining { - var needsSecondParagraph = true - let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(component.status.level) + 1) - let valueString = strings.ChannelBoost_MoreBoosts(Int32(remaining)) - switch component.subject { - case .stories: - if component.status.level == 0 { - textString = strings.ChannelBoost_EnableStoriesText(valueString).string - } else { - textString = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string - } - needsSecondParagraph = false - case let .channelReactions(reactionCount): - textString = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string - needsSecondParagraph = false - case .nameColors: - let colorLevel = component.subject.requiredLevel(context: context.component.context, configuration: premiumConfiguration) - - textString = strings.ChannelBoost_EnableNameColorLevelText("\(colorLevel)").string - case .nameIcon: - textString = strings.ChannelBoost_EnableNameIconLevelText("\(premiumConfiguration.minChannelNameIconLevel)").string - case .profileColors: - textString = strings.ChannelBoost_EnableProfileColorLevelText("\(premiumConfiguration.minChannelProfileColorLevel)").string - case .profileIcon: - textString = strings.ChannelBoost_EnableProfileIconLevelText("\(premiumConfiguration.minChannelProfileIconLevel)").string - case .emojiStatus: - textString = strings.ChannelBoost_EnableEmojiStatusLevelText("\(premiumConfiguration.minChannelEmojiStatusLevel)").string - case .wallpaper: - textString = strings.ChannelBoost_EnableWallpaperLevelText("\(premiumConfiguration.minChannelWallpaperLevel)").string - case .customWallpaper: - textString = strings.ChannelBoost_EnableCustomWallpaperLevelText("\(premiumConfiguration.minChannelCustomWallpaperLevel)").string + let level: Int + let boosts: Int + let remaining: Int? + let progress: CGFloat + let myBoostCount: Int + if let boostState = component.boostState { + level = Int(boostState.level) + boosts = Int(boostState.boosts) + if let nextLevelBoosts = boostState.nextLevelBoosts { + remaining = Int(nextLevelBoosts - boostState.boosts) + progress = CGFloat(boostState.boosts - boostState.currentLevelBoosts) / CGFloat(nextLevelBoosts - boostState.currentLevelBoosts) + } else { + remaining = nil + progress = 1.0 } - - if needsSecondParagraph { - textString += "\n\n\(strings.ChannelBoost_AskToBoost)" + myBoostCount = Int(boostState.myBoostCount) + } else if let status = component.status { + level = status.level + boosts = status.boosts + if let nextLevelBoosts = status.nextLevelBoosts { + remaining = nextLevelBoosts - status.boosts + progress = CGFloat(status.boosts - status.currentLevelBoosts) / CGFloat(nextLevelBoosts - status.currentLevelBoosts) + } else { + remaining = nil + progress = 1.0 } + myBoostCount = 0 } else { - let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(component.status.level)) - textString = strings.ChannelBoost_MaxLevelReachedTextAuthor("\(component.status.level)", storiesString).string + level = 0 + boosts = 0 + remaining = nil + progress = 0.0 + myBoostCount = 0 } - let defaultTitle = strings.ChannelBoost_Level("\(component.status.level)").string + let badgeText = "\(boosts)" + + var textString = "" + + var isCurrent = false + switch component.mode { + case let .owner(subject): + if let remaining { + var needsSecondParagraph = true + + if let subject { + let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(level) + 1) + let valueString = strings.ChannelBoost_MoreBoosts(Int32(remaining)) + switch subject { + case .stories: + if level == 0 { + textString = strings.ChannelBoost_EnableStoriesText(valueString).string + } else { + textString = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string + } + needsSecondParagraph = false + case let .channelReactions(reactionCount): + textString = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string + needsSecondParagraph = false + case .nameColors: + let colorLevel = subject.requiredLevel(group: isGroup, context: context.component.context, configuration: premiumConfiguration) + textString = strings.ChannelBoost_EnableNameColorLevelText("\(colorLevel)").string + case .nameIcon: + textString = strings.ChannelBoost_EnableNameIconLevelText("\(premiumConfiguration.minChannelNameIconLevel)").string + case .profileColors: + textString = strings.ChannelBoost_EnableProfileColorLevelText("\(premiumConfiguration.minChannelProfileColorLevel)").string + case .profileIcon: + textString = strings.ChannelBoost_EnableProfileIconLevelText("\(premiumConfiguration.minChannelProfileIconLevel)").string + case .emojiStatus: + textString = strings.ChannelBoost_EnableEmojiStatusLevelText("\(premiumConfiguration.minChannelEmojiStatusLevel)").string + case .wallpaper: + textString = strings.ChannelBoost_EnableWallpaperLevelText("\(premiumConfiguration.minChannelWallpaperLevel)").string + case .customWallpaper: + textString = strings.ChannelBoost_EnableCustomWallpaperLevelText("\(premiumConfiguration.minChannelCustomWallpaperLevel)").string + case .audioTranscription: + textString = "" + case .emojiPack: + textString = strings.ChannelBoost_EnableGroupEmojiPackLevelText("\(premiumConfiguration.minGroupEmojiPackLevel)").string + } + } else { + let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) + if myBoostCount > 0 { + textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string + } else { + textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string + } + } + + if needsSecondParagraph { + textString += "\n\n\(strings.ChannelBoost_AskToBoost)" + } + } else { + textString = strings.ChannelBoost_MaxLevelReached_Text(peerName, "\(level)").string +// let storiesString = strings.ChannelBoost_StoriesPerDay(Int32(level)) +// textString = strings.ChannelBoost_MaxLevelReachedTextAuthor("\(level)", storiesString).string + } + case let .user(mode): + switch mode { + case let .groupPeer(_, peerBoostCount): + let memberName = state.memberPeer?.compactDisplayTitle ?? "" + //TODO:localize + if myBoostCount > 0 { + if let remaining { + let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) + textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times. \(strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string)" + } else { + textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times." + } + } else { + textString = "**\(memberName)** boosted the group **\(peerBoostCount)** times. Boost **\(peerName)** to help it unlock new features and get a booster **badge** for your messages." + } + isCurrent = true + case let .unrestrict(unrestrictCount): + textString = "Boost the group **\(unrestrictCount)** times to remove messaging restrictions. Your boosts will help **\(peerName)** to unlock new features." + isCurrent = true + default: + if let remaining { + let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) + if myBoostCount > 0 { + textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string + } else { + textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string + } + } else { + textString = strings.ChannelBoost_MaxLevelReached_Text(peerName, "\(level)").string + } + isCurrent = mode == .current + } + case .features: + textString = "By gaining **boosts**, your group reaches higher levels and unlocks more features." + } + + let defaultTitle = strings.ChannelBoost_Level("\(level)").string let defaultValue = "" - let premiumValue = strings.ChannelBoost_Level("\(component.status.level + 1)").string + let premiumValue = strings.ChannelBoost_Level("\(level + 1)").string let premiumTitle = "" - let progress: CGFloat - if let nextLevelBoosts = component.status.nextLevelBoosts { - progress = CGFloat(component.status.boosts - component.status.currentLevelBoosts) / CGFloat(nextLevelBoosts - component.status.currentLevelBoosts) - } else { - progress = 1.0 - } - - let contentSize: CGSize + var contentSize: CGSize = CGSize(width: context.availableSize.width, height: 44.0) let textFont = Font.regular(15.0) let boldTextFont = Font.semibold(15.0) @@ -532,17 +698,6 @@ private final class LimitSheetContent: CombinedComponent { return (TelegramTextAttributes.URL, contents) }) - let textChild = text.update( - component: BalancedTextComponent( - text: .markdown(text: textString, attributes: markdownAttributes), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.1 - ), - availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), - transition: .immediate - ) - let gradientColors = [ UIColor(rgb: 0x0077ff), UIColor(rgb: 0x6b93ff), @@ -553,172 +708,339 @@ private final class LimitSheetContent: CombinedComponent { UIColor(rgb: 0x007afe), UIColor(rgb: 0x5494ff) ] - - let limitTransition: Transition = .immediate - - let button = button.update( - component: SolidRoundedButtonComponent( - title: actionButtonText, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: .black, - backgroundColors: buttonGradientColors, - foregroundColor: .white - ), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - gloss: false, - iconName: buttonIconName, - animationName: nil, - iconPosition: .left, - action: { - component.copyLink(component.status.url) - component.dismiss() - } - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), - transition: context.transition - ) - var buttonOffset: CGFloat = 0.0 - var textOffset: CGFloat = 184.0 - - let linkButton = linkButton.update( - component: SolidRoundedButtonComponent( - title: component.status.url.replacingOccurrences(of: "https://", with: ""), - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), - backgroundColors: [], - foregroundColor: theme.list.itemPrimaryTextColor - ), - font: .regular, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - action: { - component.copyLink(component.status.url) - component.dismiss() - } - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), - transition: context.transition - ) - buttonOffset += 66.0 - - let linkFrame = CGRect(origin: CGPoint(x: sideInset, y: textOffset + textChild.size.height + 24.0), size: linkButton.size) - context.add(linkButton - .position(CGPoint(x: linkFrame.midX, y: linkFrame.midY)) - ) - - let textSize = textChild.size - textOffset += textSize.height / 2.0 - - context.add(textChild - .position(CGPoint(x: context.availableSize.width / 2.0, y: textOffset)) - ) - - let limit = limit.update( - component: PremiumLimitDisplayComponent( - inactiveColor: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), - activeColors: gradientColors, - inactiveTitle: defaultTitle, - inactiveValue: defaultValue, - inactiveTitleColor: theme.list.itemPrimaryTextColor, - activeTitle: premiumTitle, - activeValue: premiumValue, - activeTitleColor: .white, - badgeIconName: iconName, - badgeText: badgeText, - badgePosition: progress, - badgeGraphPosition: progress, - invertProgress: true, - isPremiumDisabled: false - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), - transition: limitTransition - ) - context.add(limit - .position(CGPoint(x: context.availableSize.width / 2.0, y: limit.size.height / 2.0 + 44.0)) - ) - - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: textOffset + ceil(textSize.height / 2.0) + buttonOffset + 24.0), size: button.size) - context.add(button - .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) - ) - - var additionalContentHeight: CGFloat = 0.0 - - if premiumConfiguration.giveawayGiftsPurchaseAvailable { - let orText = orText.update( - component: MultilineTextComponent(text: .plain(NSAttributedString(string: strings.ChannelBoost_Or, font: Font.regular(15.0), textColor: textColor.withAlphaComponent(0.8), paragraphAlignment: .center))), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), - transition: .immediate - ) - context.add(orText - .position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.maxY + 27.0)) - ) + if case let .user(mode) = component.mode, case .external = mode, let peer = state.peer { + contentSize.height += 10.0 - let orLeftLine = orLeftLine.update( - component: Rectangle(color: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3)), - availableSize: CGSize(width: 90.0, height: 1.0 - UIScreenPixel), - transition: .immediate - ) - context.add(orLeftLine - .position(CGPoint(x: context.availableSize.width / 2.0 - orText.size.width / 2.0 - 11.0 - 45.0, y: buttonFrame.maxY + 27.0)) - ) - - let orRightLine = orRightLine.update( - component: Rectangle(color: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3)), - availableSize: CGSize(width: 90.0, height: 1.0 - UIScreenPixel), - transition: .immediate - ) - context.add(orRightLine - .position(CGPoint(x: context.availableSize.width / 2.0 + orText.size.width / 2.0 + 11.0 + 45.0, y: buttonFrame.maxY + 27.0)) - ) - - if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== theme { - state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme) - } - - - let giftString = strings.Premium_BoostByGiftDescription2 - let giftAttributedString = parseMarkdownIntoAttributedString(giftString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString - - if let range = giftAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { - giftAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: giftAttributedString.string)) - } - let giftText = giftText.update( - component: BalancedTextComponent( - text: .plain(giftAttributedString), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.1, - highlightColor: linkColor.withAlphaComponent(0.2), - highlightAction: { _ in - return nil - }, - tapAction: { _, _ in - component.openGift?() + let peerShortcut = peerShortcut.update( + component: Button( + content: AnyComponent( + PeerShortcutComponent( + context: component.context, + theme: component.theme, + peer: peer + + ) + ), + action: { + component.dismiss() + Queue.mainQueue().after(0.35) { + component.openPeer?(peer) + } } ), + availableSize: CGSize(width: context.availableSize.width - 32.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(peerShortcut + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + peerShortcut.size.height / 2.0)) + ) + contentSize.height += peerShortcut.size.height + 2.0 + } + + if case .features = component.mode { + contentSize.height -= 14.0 + + let iconSize = CGSize(width: 90.0, height: 90.0) + let gradientImage: UIImage + if let current = state.cachedIconImage { + gradientImage = current + } else { + gradientImage = generateFilledCircleImage(diameter: iconSize.width, color: theme.actionSheet.controlAccentColor)! + context.state.cachedIconImage = gradientImage + } + + let iconBackground = iconBackground.update( + component: Image(image: gradientImage), + availableSize: iconSize, + transition: .immediate + ) + context.add(iconBackground + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)) + ) + + let icon = icon.update( + component: BundleIconComponent( + name: "Premium/BoostLarge", + tintColor: .white + ), + availableSize: CGSize(width: 90.0, height: 90.0), + transition: .immediate + ) +// let icon = icon.update( +// component: LottieComponent( +// content: LottieComponent.AppBundleContent(name: iconName), +// playOnce: state.playOnce +// ), +// availableSize: CGSize(width: 70, height: 70), +// transition: .immediate +// ) + + context.add(icon + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)) + ) + contentSize.height += iconSize.height + contentSize.height += 52.0 + } else { + let limit = limit.update( + component: PremiumLimitDisplayComponent( + inactiveColor: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), + activeColors: gradientColors, + inactiveTitle: defaultTitle, + inactiveValue: defaultValue, + inactiveTitleColor: theme.list.itemPrimaryTextColor, + activeTitle: premiumTitle, + activeValue: premiumValue, + activeTitleColor: .white, + badgeIconName: iconName, + badgeText: badgeText, + badgePosition: progress, + badgeGraphPosition: progress, + invertProgress: true, + isPremiumDisabled: false + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: context.transition + ) + context.add(limit + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + limit.size.height / 2.0)) + ) + + contentSize.height += limit.size.height + 23.0 + } + + if myBoostCount > 0 { + let alternateTitle = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peerName).string : strings.ChannelBoost_YouBoostedOtherChannelText + + var alternateBadge: String? + if myBoostCount > 1 { + alternateBadge = "X\(myBoostCount)" + } + + let alternateText = alternateText.update( + component: List( + [ + AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + BoostedTitleContent(text: NSAttributedString(string: alternateTitle, font: Font.semibold(15.0), textColor: textColor), badge: alternateBadge) + ) + ), + AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + BalancedTextComponent( + text: .markdown(text: textString, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1 + ) + ) + ) + ], + centerAlignment: true + ), availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), transition: .immediate ) - context.add(giftText - .position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.maxY + 50.0 + giftText.size.height / 2.0)) + context.add(alternateText + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + alternateText.size.height / 2.0)) + .appear(Transition.Appear({ _, view, transition in + transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: 64.0), to: .zero, additive: true) + transition.animateAlpha(view: view, from: 0.0, to: 1.0) + })) + .disappear(Transition.Disappear({ view, transition, completion in + view.superview?.sendSubviewToBack(view) + transition.animatePosition(view: view, from: .zero, to: CGPoint(x: 0.0, y: -64.0), additive: true) + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in + completion() + }) + })) + ) + contentSize.height += alternateText.size.height + 20.0 + } else { + let text = text.update( + component: BalancedTextComponent( + text: .markdown(text: textString, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) + .appear(Transition.Appear({ _, view, transition in + transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: 64.0), to: .zero, additive: true) + transition.animateAlpha(view: view, from: 0.0, to: 1.0) + })) + .disappear(Transition.Disappear({ view, transition, completion in + view.superview?.sendSubviewToBack(view) + transition.animatePosition(view: view, from: .zero, to: CGPoint(x: 0.0, y: -64.0), additive: true) + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in + completion() + }) + })) + ) + contentSize.height += text.size.height + 20.0 + } + + if case .owner = component.mode, let status = component.status { + contentSize.height += 7.0 + + let linkButton = linkButton.update( + component: SolidRoundedButtonComponent( + title: status.url.replacingOccurrences(of: "https://", with: ""), + theme: SolidRoundedButtonComponent.Theme( + backgroundColor: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3), + backgroundColors: [], + foregroundColor: theme.list.itemPrimaryTextColor + ), + font: .regular, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + action: { + component.copyLink(status.url) + component.dismiss() + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + context.add(linkButton + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + linkButton.size.height / 2.0)) + ) + contentSize.height += linkButton.size.height + 16.0 + + //TODO:localize + let boostButton = boostButton.update( + component: SolidRoundedButtonComponent( + title: "Boost", + theme: SolidRoundedButtonComponent.Theme( + backgroundColor: .black, + backgroundColors: buttonGradientColors, + foregroundColor: .white + ), + font: .bold, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: false, + iconName: nil, + animationName: nil, + iconPosition: .left, + action: { + component.boost() + } + ), + availableSize: CGSize(width: (context.availableSize.width - 8.0 - sideInset * 2.0) / 2.0, height: 50.0), + transition: context.transition ) - additionalContentHeight += giftText.size.height + 50.0 + let copyButton = copyButton.update( + component: SolidRoundedButtonComponent( + title: "Copy", + theme: SolidRoundedButtonComponent.Theme( + backgroundColor: .black, + backgroundColors: buttonGradientColors, + foregroundColor: .white + ), + font: .bold, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: false, + iconName: nil, + animationName: nil, + iconPosition: .left, + action: { + component.copyLink(status.url) + component.dismiss() + } + ), + availableSize: CGSize(width: (context.availableSize.width - 8.0 - sideInset * 2.0) / 2.0, height: 50.0), + transition: context.transition + ) + + let boostButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentSize.height), size: boostButton.size) + context.add(boostButton + .position(boostButtonFrame.center) + ) + let copyButtonFrame = CGRect(origin: CGPoint(x: context.availableSize.width - sideInset - copyButton.size.width, y: contentSize.height), size: copyButton.size) + context.add(copyButton + .position(copyButtonFrame.center) + ) + contentSize.height += boostButton.size.height + + if premiumConfiguration.giveawayGiftsPurchaseAvailable { + let orText = orText.update( + component: MultilineTextComponent(text: .plain(NSAttributedString(string: strings.ChannelBoost_Or, font: Font.regular(15.0), textColor: textColor.withAlphaComponent(0.8), paragraphAlignment: .center))), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(orText + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + 27.0)) + ) + + let orLeftLine = orLeftLine.update( + component: Rectangle(color: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3)), + availableSize: CGSize(width: 90.0, height: 1.0 - UIScreenPixel), + transition: .immediate + ) + context.add(orLeftLine + .position(CGPoint(x: context.availableSize.width / 2.0 - orText.size.width / 2.0 - 11.0 - 45.0, y: contentSize.height + 27.0)) + ) + + let orRightLine = orRightLine.update( + component: Rectangle(color: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.3)), + availableSize: CGSize(width: 90.0, height: 1.0 - UIScreenPixel), + transition: .immediate + ) + context.add(orRightLine + .position(CGPoint(x: context.availableSize.width / 2.0 + orText.size.width / 2.0 + 11.0 + 45.0, y: contentSize.height + 27.0)) + ) + + if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== theme { + state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme) + } + + let giftString = isGroup ? strings.Premium_Group_BoostByGiftDescription : strings.Premium_BoostByGiftDescription2 + let giftAttributedString = parseMarkdownIntoAttributedString(giftString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + + if let range = giftAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { + giftAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: giftAttributedString.string)) + } + let giftText = giftText.update( + component: BalancedTextComponent( + text: .plain(giftAttributedString), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1, + highlightColor: linkColor.withAlphaComponent(0.2), + highlightAction: { _ in + return nil + }, + tapAction: { _, _ in + component.openGift?() + } + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(giftText + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + 50.0 + giftText.size.height / 2.0)) + ) + contentSize.height += giftText.size.height + 50.0 + 23.0 + } } - var nextLevels: ClosedRange? - if component.status.level < 10 { - nextLevels = Int32(component.status.level) + 1 ... 10 + if level < 10 { + nextLevels = Int32(level) + 1 ... 10 } - - var levelsHeight: CGFloat = 0.0 + var levelItems: [AnyComponentWithIdentity] = [] var nameColorsAtLevel: [(Int32, Int32)] = [] @@ -735,23 +1057,36 @@ private final class LimitSheetContent: CombinedComponent { for (key, value) in nameColorsCountMap { nameColorsAtLevel.append((key, value)) } + + var isFeatures = false + if case .features = component.mode { + isFeatures = true + } if let nextLevels { for level in nextLevels { var perks: [LevelSectionComponent.Perk] = [] + perks.append(.story(level)) - perks.append(.reaction(level)) - + + if !isGroup { + perks.append(.reaction(level)) + } + var nameColorsCount: Int32 = 0 for (colorLevel, count) in nameColorsAtLevel { if level >= colorLevel && colorLevel == 1 { nameColorsCount = count } } - if nameColorsCount > 0 { + if !isGroup && nameColorsCount > 0 { perks.append(.nameColor(nameColorsCount)) } + if isGroup && level >= premiumConfiguration.minGroupAudioTranscriptionLevel { + perks.append(.audioTranscription) + } + if level >= premiumConfiguration.minChannelProfileColorLevel { let delta = min(level - premiumConfiguration.minChannelProfileColorLevel + 1, 2) perks.append(.profileColor(8 * delta)) @@ -760,17 +1095,21 @@ private final class LimitSheetContent: CombinedComponent { perks.append(.profileIcon) } + if isGroup && level >= premiumConfiguration.minGroupAudioTranscriptionLevel { + perks.append(.emojiPack) + } + var linkColorsCount: Int32 = 0 for (colorLevel, count) in nameColorsAtLevel { if level >= colorLevel { linkColorsCount += count } } - if linkColorsCount > 0 { + if !isGroup && linkColorsCount > 0 { perks.append(.linkColor(linkColorsCount)) } - if level >= premiumConfiguration.minChannelNameIconLevel { + if !isGroup && level >= premiumConfiguration.minChannelNameIconLevel { perks.append(.linkIcon) } if level >= premiumConfiguration.minChannelEmojiStatusLevel { @@ -790,8 +1129,9 @@ private final class LimitSheetContent: CombinedComponent { theme: component.theme, strings: component.strings, level: level, - isFirst: levelItems.isEmpty, - perks: perks + isFirst: !isFeatures && levelItems.isEmpty, + perks: perks.reversed(), + isGroup: isGroup ) ) ) @@ -806,14 +1146,12 @@ private final class LimitSheetContent: CombinedComponent { transition: context.transition ) context.add(levels - .position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.maxY + 23.0 + additionalContentHeight + levels.size.height / 2.0 )) + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + levels.size.height / 2.0 )) ) - levelsHeight = levels.size.height + 40.0 + contentSize.height += levels.size.height + 80.0 + contentSize.height += 60.0 } - - let bottomInset: CGFloat = 0.0 - contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + additionalContentHeight + 5.0 + bottomInset + levelsHeight) - + return contentSize } } @@ -824,36 +1162,45 @@ private final class BoostLevelsContainerComponent: CombinedComponent { let theme: PresentationTheme let strings: PresentationStrings - let peer: EnginePeer - let subject: BoostSubject - let status: ChannelBoostStatus + let peerId: EnginePeer.Id + let mode: PremiumBoostLevelsScreen.Mode + let status: ChannelBoostStatus? + let boostState: InternalBoostState.DisplayData? + let boost: () -> Void let copyLink: (String) -> Void let dismiss: () -> Void - let openStats: () -> Void - let openGift: () -> Void + let openStats: (() -> Void)? + let openGift: (() -> Void)? + let openPeer: ((EnginePeer) -> Void)? init( context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, - peer: EnginePeer, - subject: BoostSubject, - status: ChannelBoostStatus, + peerId: EnginePeer.Id, + mode: PremiumBoostLevelsScreen.Mode, + status: ChannelBoostStatus?, + boostState: InternalBoostState.DisplayData?, + boost: @escaping () -> Void, copyLink: @escaping (String) -> Void, dismiss: @escaping () -> Void, - openStats: @escaping () -> Void, - openGift: @escaping () -> Void + openStats: (() -> Void)?, + openGift: (() -> Void)?, + openPeer: ((EnginePeer) -> Void)? ) { self.context = context self.theme = theme self.strings = strings - self.peer = peer - self.subject = subject + self.peerId = peerId + self.mode = mode self.status = status + self.boostState = boostState + self.boost = boost self.copyLink = copyLink self.dismiss = dismiss self.openStats = openStats self.openGift = openGift + self.openPeer = openPeer } static func ==(lhs: BoostLevelsContainerComponent, rhs: BoostLevelsContainerComponent) -> Bool { @@ -863,15 +1210,18 @@ private final class BoostLevelsContainerComponent: CombinedComponent { if lhs.theme !== rhs.theme { return false } - if lhs.peer != rhs.peer { + if lhs.peerId != rhs.peerId { return false } - if lhs.subject != rhs.subject { + if lhs.mode != rhs.mode { return false } if lhs.status != rhs.status { return false } + if lhs.boostState != rhs.boostState { + return false + } return true } @@ -879,10 +1229,30 @@ private final class BoostLevelsContainerComponent: CombinedComponent { var topContentOffset: CGFloat = 0.0 var cachedStatsImage: (UIImage, PresentationTheme)? var cachedCloseImage: (UIImage, PresentationTheme)? + + private var disposable: Disposable? + private(set) var peer: EnginePeer? + + init(context: AccountContext, peerId: EnginePeer.Id) { + super.init() + + self.disposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).startStrict(next: { [weak self] peer in + guard let self else { + return + } + self.peer = peer + self.updated() + }) + } + + deinit { + self.disposable?.dispose() + } } func makeState() -> State { - return State() + return State(context: self.context, peerId: self.peerId) } static var body: Body { @@ -904,21 +1274,29 @@ private final class BoostLevelsContainerComponent: CombinedComponent { let component = context.component + var isGroup = false + if let peer = state.peer, case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + let scroll = scroll.update( component: ScrollComponent( content: AnyComponent( - LimitSheetContent( + SheetContent( context: component.context, theme: component.theme, strings: component.strings, insets: .zero, - peer: component.peer, - subject: component.subject, + peerId: component.peerId, + mode: component.mode, status: component.status, + boostState: component.boostState, + boost: component.boost, copyLink: component.copyLink, dismiss: component.dismiss, openStats: component.openStats, - openGift: component.openGift + openGift: component.openGift, + openPeer: component.openPeer ) ), contentInsets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 34.0, right: 0.0), @@ -964,38 +1342,71 @@ private final class BoostLevelsContainerComponent: CombinedComponent { ) let titleString: String - if let _ = component.status.nextLevelBoosts { - switch component.subject { - case .stories: - if component.status.level == 0 { - titleString = strings.ChannelBoost_EnableStories - } else { - titleString = strings.ChannelBoost_IncreaseLimit - } - case .nameColors: - titleString = strings.ChannelBoost_NameColor - case .nameIcon: - titleString = strings.ChannelBoost_NameIcon - case .profileColors: - titleString = strings.ChannelBoost_ProfileColor - case .profileIcon: - titleString = strings.ChannelBoost_ProfileIcon - case .channelReactions: - titleString = strings.ChannelBoost_CustomReactions - case .emojiStatus: - titleString = strings.ChannelBoost_EmojiStatus - case .wallpaper: - titleString = strings.ChannelBoost_Wallpaper - case .customWallpaper: - titleString = strings.ChannelBoost_CustomWallpaper - } - } else { - titleString = strings.ChannelBoost_MaxLevelReached - } + var titleFont = Font.semibold(17.0) + switch component.mode { + case let .owner(subject): + if let status = component.status, let _ = status.nextLevelBoosts { + if let subject { + switch subject { + case .stories: + if status.level == 0 { + titleString = strings.ChannelBoost_EnableStories + } else { + titleString = strings.ChannelBoost_IncreaseLimit + } + case .nameColors: + titleString = strings.ChannelBoost_NameColor + case .nameIcon: + titleString = strings.ChannelBoost_NameIcon + case .profileColors: + titleString = strings.ChannelBoost_ProfileColor + case .profileIcon: + titleString = strings.ChannelBoost_ProfileIcon + case .channelReactions: + titleString = strings.ChannelBoost_CustomReactions + case .emojiStatus: + titleString = strings.ChannelBoost_EmojiStatus + case .wallpaper: + titleString = strings.ChannelBoost_Wallpaper + case .customWallpaper: + titleString = strings.ChannelBoost_CustomWallpaper + case .audioTranscription: + //TODO:localize + titleString = "Audio Transcription" + case .emojiPack: + titleString = "Set Group Emoji Pack" + } + } else { + titleString = isGroup ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current + } + } else { + titleString = strings.ChannelBoost_MaxLevelReached + } + case let .user(mode): + var remaining: Int? + if let status = component.status, let nextLevelBoosts = status.nextLevelBoosts { + remaining = nextLevelBoosts - status.boosts + } + + if let _ = remaining { + if case .current = mode { + titleString = isGroup ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current + } else { + titleString = isGroup ? strings.GroupBoost_Title_Other : strings.ChannelBoost_Title_Other + } + } else { + titleString = strings.ChannelBoost_MaxLevelReached + } + case .features: + //TODO:localize + titleString = "Additional Features" + titleFont = Font.semibold(20.0) + } + let title = title.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)), + text: .plain(NSAttributedString(string: titleString, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)), horizontalAlignment: .center, truncationType: .end, maximumNumberOfLines: 1 @@ -1004,7 +1415,29 @@ private final class BoostLevelsContainerComponent: CombinedComponent { transition: context.transition ) - let topPanelAlpha: CGFloat = min(30.0, state.topContentOffset) / 30.0 + let topPanelAlpha: CGFloat + let titleOriginY: CGFloat + let titleScale: CGFloat + if case .features = component.mode { + if state.topContentOffset > 78.0 { + topPanelAlpha = min(30.0, state.topContentOffset - 78.0) / 30.0 + } else { + topPanelAlpha = 0.0 + } + + let titleTopOriginY = topPanel.size.height / 2.0 + let titleBottomOriginY: CGFloat = 146.0 + let titleOriginDelta = titleTopOriginY - titleBottomOriginY + + let fraction = min(1.0, state.topContentOffset / abs(titleOriginDelta)) + titleOriginY = titleBottomOriginY + fraction * titleOriginDelta + titleScale = 1.0 - max(0.0, fraction * 0.2) + } else { + topPanelAlpha = min(30.0, state.topContentOffset) / 30.0 + titleOriginY = topPanel.size.height / 2.0 + titleScale = 1.0 + } + context.add(topPanel .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0)) .opacity(topPanelAlpha) @@ -1014,30 +1447,33 @@ private final class BoostLevelsContainerComponent: CombinedComponent { .opacity(topPanelAlpha) ) context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: titleOriginY)) + .scale(titleScale) ) - let statsButton = statsButton.update( - component: Button( - content: AnyComponent( - BundleIconComponent( - name: "Premium/Stats", - tintColor: component.theme.list.itemAccentColor - ) - ), - action: { - component.dismiss() - Queue.mainQueue().after(0.35) { - component.openStats() + if let openStats = component.openStats { + let statsButton = statsButton.update( + component: Button( + content: AnyComponent( + BundleIconComponent( + name: "Premium/Stats", + tintColor: component.theme.list.itemAccentColor + ) + ), + action: { + component.dismiss() + Queue.mainQueue().after(0.35) { + openStats() + } } - } - ).minSize(CGSize(width: 44.0, height: 44.0)), - availableSize: context.availableSize, - transition: .immediate - ) - context.add(statsButton - .position(CGPoint(x: 31.0, y: 28.0)) - ) + ).minSize(CGSize(width: 44.0, height: 44.0)), + availableSize: context.availableSize, + transition: .immediate + ) + context.add(statsButton + .position(CGPoint(x: 31.0, y: 28.0)) + ) + } let closeImage: UIImage if let (image, theme) = state.cachedCloseImage, theme === component.theme { @@ -1066,6 +1502,18 @@ private final class BoostLevelsContainerComponent: CombinedComponent { } public class PremiumBoostLevelsScreen: ViewController { + public enum Mode: Equatable { + public enum UserMode: Equatable { + case external + case current + case groupPeer(EnginePeer.Id, Int) + case unrestrict(Int) + } + case user(mode: UserMode) + case owner(subject: BoostSubject?) + case features + } + final class Node: ViewControllerTracingNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private var presentationData: PresentationData private weak var controller: PremiumBoostLevelsScreen? @@ -1075,17 +1523,18 @@ public class PremiumBoostLevelsScreen: ViewController { let containerView: UIView let contentView: ComponentHostView + let footerContainerView: UIView + let footerView: ComponentHostView private(set) var isExpanded = false private var panGestureRecognizer: UIPanGestureRecognizer? private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? + private let hapticFeedback = HapticFeedback() + private var currentIsVisible: Bool = false private var currentLayout: ContainerViewLayout? - - var isPremium: Bool? - var disposable: Disposable? - + init(context: AccountContext, controller: PremiumBoostLevelsScreen) { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } if controller.forceDark { @@ -1103,6 +1552,9 @@ public class PremiumBoostLevelsScreen: ViewController { self.containerView = UIView() self.contentView = ComponentHostView() + self.footerContainerView = UIView() + self.footerView = ComponentHostView() + super.init() self.containerView.clipsToBounds = true @@ -1113,10 +1565,55 @@ public class PremiumBoostLevelsScreen: ViewController { self.view.addSubview(self.wrappingView) self.wrappingView.addSubview(self.containerView) self.containerView.addSubview(self.contentView) - } - - deinit { - self.disposable?.dispose() + + if case .user = controller.mode, let status = controller.status { + self.containerView.addSubview(self.footerContainerView) + self.footerContainerView.addSubview(self.footerView) + + var myBoostCount: Int32 = 0 + var currentMyBoostCount: Int32 = 0 + var availableBoosts: [MyBoostStatus.Boost] = [] + var occupiedBoosts: [MyBoostStatus.Boost] = [] + if let myBoostStatus = controller.myBoostStatus { + for boost in myBoostStatus.boosts { + if let boostPeer = boost.peer { + if boostPeer.id == controller.peerId { + myBoostCount += 1 + } else { + occupiedBoosts.append(boost) + } + } else { + availableBoosts.append(boost) + } + } + } + + let boosts = max(Int32(status.boosts), myBoostCount) + let initialState = InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: boosts) + self.boostState = initialState.displayData(myBoostCount: myBoostCount, currentMyBoostCount: 0, replacedBoosts: controller.replacedBoosts?.0) + + self.updatedState.set(.single(InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: boosts + 1))) + + if let (replacedBoosts, inChannels) = controller.replacedBoosts { + currentMyBoostCount += 1 + + self.boostState = initialState.displayData(myBoostCount: myBoostCount, currentMyBoostCount: 1) + Queue.mainQueue().justDispatch { + self.updated(transition: .easeInOut(duration: 0.2)) + } + + Queue.mainQueue().after(0.3) { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let undoController = UndoOverlayController(presentationData: presentationData, content: .image(image: generateTintedImage(image: UIImage(bundleImageName: "Premium/BoostReplaceIcon"), color: .white)!, title: nil, text: presentationData.strings.ReassignBoost_Success(presentationData.strings.ReassignBoost_Boosts(replacedBoosts), presentationData.strings.ReassignBoost_OtherChannels(inChannels)).string, round: false, undoText: nil), elevatedLayout: false, position: .top, action: { _ in return true }) + controller.present(undoController, in: .current) + } + } + + self.availableBoosts = availableBoosts + self.occupiedBoosts = occupiedBoosts + self.myBoostCount = myBoostCount + self.currentMyBoostCount = currentMyBoostCount + } } override func didLoad() { @@ -1190,6 +1687,9 @@ public class PremiumBoostLevelsScreen: ViewController { private var dismissOffset: CGFloat? func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) { + guard !self.isDismissing else { + return + } self.currentLayout = layout self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0)) @@ -1267,26 +1767,51 @@ public class PremiumBoostLevelsScreen: ViewController { transition.setFrame(view: self.containerView, frame: clipFrame) + + var footerHeight: CGFloat = 8.0 + 50.0 + footerHeight += layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : 8.0 + + let convertedFooterFrame = self.view.convert(CGRect(origin: CGPoint(x: clipFrame.minX, y: clipFrame.maxY - footerHeight), size: CGSize(width: clipFrame.width, height: footerHeight)), to: self.containerView) + transition.setFrame(view: self.footerContainerView, frame: convertedFooterFrame) + self.updated(transition: transition) } + private var boostState: InternalBoostState.DisplayData? func updated(transition: Transition) { - guard let controller = self.controller else { + guard let controller = self.controller, let layout = self.currentLayout else { return } - let containerSize = self.containerView.bounds.size - - let contentSize = self.contentView.update( - transition: .immediate, + transition: transition, component: AnyComponent( BoostLevelsContainerComponent( context: controller.context, theme: self.presentationData.theme, strings: self.presentationData.strings, - peer: controller.peer, - subject: controller.subject, + peerId: controller.peerId, + mode: controller.mode, status: controller.status, + boostState: self.boostState, + boost: { [weak controller] in + guard let controller, let navigationController = controller.navigationController else { + return + } + + controller.dismiss(animated: true) + + Queue.mainQueue().justDispatch { + let boostController = PremiumBoostLevelsScreen( + context: controller.context, + peerId: controller.peerId, + mode: .user(mode: .current), + status: controller.status, + myBoostStatus: nil, + forceDark: controller.forceDark + ) + navigationController.pushViewController(boostController, animated: true) + } + }, copyLink: { [weak self, weak controller] link in guard let self else { return @@ -1301,13 +1826,37 @@ public class PremiumBoostLevelsScreen: ViewController { controller?.dismiss(animated: true) }, openStats: controller.openStats, - openGift: controller.openGift + openGift: controller.openGift, + openPeer: controller.openPeer ) ), environment: {}, - containerSize: CGSize(width: containerSize.width, height: containerSize.height) + containerSize: self.containerView.bounds.size ) self.contentView.frame = CGRect(origin: .zero, size: contentSize) + + var footerHeight: CGFloat = 8.0 + 50.0 + footerHeight += layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : 8.0 + + let footerSize = self.footerView.update( + transition: .immediate, + component: AnyComponent( + FooterComponent( + context: controller.context, + theme: self.presentationData.theme, + title: self.currentMyBoostCount > 0 ? "Boost Again" : "Boost Group", + action: { [weak self] in + guard let self else { + return + } + self.buttonPressed() + } + ) + ), + environment: {}, + containerSize: CGSize(width: self.containerView.bounds.width, height: footerHeight) + ) + self.footerView.frame = CGRect(origin: .zero, size: footerSize) } private var didPlayAppearAnimation = false @@ -1337,8 +1886,7 @@ public class PremiumBoostLevelsScreen: ViewController { let bottomInset: CGFloat = layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : bottomPanelPadding let panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + 28.0 - let additionalInset: CGFloat = 0.0 - return layout.size.height - layout.size.width - 181.0 - panelHeight + additionalInset + return layout.size.height - layout.size.width - 128.0 - panelHeight } else { return 210.0 } @@ -1552,6 +2100,227 @@ public class PremiumBoostLevelsScreen: ViewController { } self.containerLayoutUpdated(layout: layout, transition: Transition(transition)) } + + private var currentMyBoostCount: Int32 = 0 + private var myBoostCount: Int32 = 0 + private var availableBoosts: [MyBoostStatus.Boost] = [] + private var occupiedBoosts: [MyBoostStatus.Boost] = [] + private let updatedState = Promise() + + private func updateBoostState() { + guard let controller = self.controller else { + return + } + let context = controller.context + let peerId = controller.peerId + let mode = controller.mode + let status = controller.status + let isPremium = controller.context.isPremium + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 })) + let canBoostAgain = premiumConfiguration.boostsPerGiftCount > 0 + let presentationData = self.presentationData + let forceDark = controller.forceDark + + if let _ = status?.nextLevelBoosts { + if let availableBoost = self.availableBoosts.first { + self.currentMyBoostCount += 1 + self.myBoostCount += 1 + + let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + self?.updatedState.set(context.engine.peers.getChannelBoostStatus(peerId: peerId) + |> map { status in + if let status { + return InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1)) + } else { + return nil + } + }) + }) + + let _ = (self.updatedState.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] state in + guard let self, let state else { + return + } + self.boostState = state.displayData(myBoostCount: self.myBoostCount, currentMyBoostCount: self.currentMyBoostCount) + self.updated(transition: .easeInOut(duration: 0.2)) + + self.animateSuccess() + }) + + self.availableBoosts.removeFirst() + } else if !self.occupiedBoosts.isEmpty, let myBoostStatus = controller.myBoostStatus { + if canBoostAgain { + let navigationController = controller.navigationController + let openPeer = controller.openPeer + + var dismissReplaceImpl: (() -> Void)? + let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in + var channelIds = Set() + for boost in myBoostStatus.boosts { + if slots.contains(boost.slot) { + if let peer = boost.peer { + channelIds.insert(peer.id) + } + } + } + + let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: { + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { boostStatus, myBoostStatus in + dismissReplaceImpl?() + + let levelsController = PremiumBoostLevelsScreen( + context: context, + peerId: peerId, + mode: mode, + status: status, + myBoostStatus: myBoostStatus, + replacedBoosts: (Int32(slots.count), Int32(channelIds.count)), + openStats: nil, openGift: nil, openPeer: openPeer, forceDark: forceDark + ) + if let navigationController { + navigationController.pushViewController(levelsController, animated: true) + } + }) + }) + }) + + if let navigationController = controller.navigationController { + controller.dismiss(animated: true) + navigationController.pushViewController(replaceController, animated: true) + } + + dismissReplaceImpl = { [weak replaceController] in + replaceController?.dismiss(animated: true) + } + } else if let boost = self.occupiedBoosts.first, let occupiedPeer = boost.peer { + if let cooldown = boost.cooldownUntil { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let timeout = cooldown - currentTime + let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime, preferLowerValue: false) + let alertController = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: presentationData.strings.ChannelBoost_Error_BoostTooOftenTitle, + text: presentationData.strings.ChannelBoost_Error_BoostTooOftenText(valueText).string, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ], + parseMarkdown: true + ) + controller.present(alertController, in: .window(.root)) + } else { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak controller] peer in + guard let peer, let controller else { + return + } + let replaceController = replaceBoostConfirmationController(context: context, fromPeers: [occupiedPeer], toPeer: peer, commit: { [weak self] in + self?.currentMyBoostCount += 1 + self?.myBoostCount += 1 + let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [boost.slot]) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + guard let self else { + return + } + let _ = (self.updatedState.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] state in + guard let self, let state else { + return + } + self.boostState = state.displayData(myBoostCount: self.myBoostCount, currentMyBoostCount: self.currentMyBoostCount) + self.updated(transition: .easeInOut(duration: 0.2)) + + self.animateSuccess() + }) + }) + }) + controller.present(replaceController, in: .window(.root)) + }) + } + } else { + controller.dismiss(animated: true, completion: nil) + } + } else { + if isPremium { + if !canBoostAgain { + controller.dismiss(animated: true, completion: nil) + } else { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak controller] peer in + guard let peer, let controller else { + return + } + let alertController = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: presentationData.strings.ChannelBoost_MoreBoosts_Title, + text: presentationData.strings.ChannelBoost_MoreBoosts_Text(peer.compactDisplayTitle, "\(premiumConfiguration.boostsPerGiftCount)").string, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.ChannelBoost_MoreBoosts_Gift, action: { [weak controller] in + if let navigationController = controller?.navigationController { + controller?.dismiss(animated: true, completion: nil) + + Queue.mainQueue().after(0.4) { + let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost) + navigationController.pushViewController(giftController, animated: true) + } + } + }), + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Close, action: {}) + ], + actionLayout: .vertical, + parseMarkdown: true + ) + controller.present(alertController, in: .window(.root)) + }) + } + } else { + let alertController = textAlertController( + sharedContext: context.sharedContext, + updatedPresentationData: nil, + title: presentationData.strings.ChannelBoost_Error_PremiumNeededTitle, + text: presentationData.strings.ChannelBoost_Error_PremiumNeededText, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { [weak controller] in + if let navigationController = controller?.navigationController { + controller?.dismiss(animated: true) + + let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: forceDark, dismissed: nil) + navigationController.pushViewController(premiumController, animated: true) + } + }) + ], + parseMarkdown: true + ) + controller.present(alertController, in: .window(.root)) + } + } + } else { + controller.dismiss(animated: true) + } + } + + func buttonPressed() { + self.updateBoostState() + } + + private func animateSuccess() { + self.hapticFeedback.impact() + self.view.addSubview(ConfettiView(frame: self.view.bounds)) + + if self.isExpanded { + self.update(isExpanded: false, transition: .animated(duration: 0.4, curve: .spring)) + } + } } var node: Node { @@ -1559,11 +2328,14 @@ public class PremiumBoostLevelsScreen: ViewController { } private let context: AccountContext - private let peer: EnginePeer - private let subject: BoostSubject - private let status: ChannelBoostStatus - private let openStats: () -> Void - private let openGift: () -> Void + private let peerId: EnginePeer.Id + private let mode: Mode + private let status: ChannelBoostStatus? + private let myBoostStatus: MyBoostStatus? + private let replacedBoosts: (Int32, Int32)? + private let openStats: (() -> Void)? + private let openGift: (() -> Void)? + private let openPeer: ((EnginePeer) -> Void)? private let forceDark: Bool private var currentLayout: ContainerViewLayout? @@ -1572,19 +2344,25 @@ public class PremiumBoostLevelsScreen: ViewController { public init( context: AccountContext, - peer: EnginePeer, - subject: BoostSubject, - status: ChannelBoostStatus, - openStats: @escaping () -> Void, - openGift: @escaping () -> Void, + peerId: EnginePeer.Id, + mode: Mode, + status: ChannelBoostStatus?, + myBoostStatus: MyBoostStatus? = nil, + replacedBoosts: (Int32, Int32)? = nil, + openStats: (() -> Void)? = nil, + openGift: (() -> Void)? = nil, + openPeer: ((EnginePeer) -> Void)? = nil, forceDark: Bool = false ) { self.context = context - self.peer = peer - self.subject = subject + self.peerId = peerId + self.mode = mode self.status = status + self.myBoostStatus = myBoostStatus + self.replacedBoosts = replacedBoosts self.openStats = openStats self.openGift = openGift + self.openPeer = openPeer self.forceDark = forceDark super.init(navigationBarPresentationData: nil) @@ -1593,11 +2371,6 @@ public class PremiumBoostLevelsScreen: ViewController { self.statusBar.statusBarStyle = .Ignore self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - - -// UIPasteboard.general.string = link -// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } -// self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current) } required public init(coder aDecoder: NSCoder) { @@ -1607,11 +2380,7 @@ public class PremiumBoostLevelsScreen: ViewController { deinit { self.disposed() } - - @objc private func cancelPressed() { - self.dismiss(animated: true, completion: nil) - } - + override open func loadDisplayNode() { self.displayNode = Node(context: self.context, controller: self) self.displayNodeDidLoad() @@ -1651,3 +2420,160 @@ public class PremiumBoostLevelsScreen: ViewController { self.node.containerLayoutUpdated(layout: layout, transition: Transition(transition)) } } + +private final class FooterComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let title: String + let action: () -> Void + + init(context: AccountContext, theme: PresentationTheme, title: String, action: @escaping () -> Void) { + self.context = context + self.theme = theme + self.title = title + self.action = action + } + + static func ==(lhs: FooterComponent, rhs: FooterComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.title != rhs.title { + return false + } + return true + } + + final class View: UIView { + private let backgroundView: BlurredBackgroundView + private let separator = SimpleLayer() + + private let button = ComponentView() + + private var component: FooterComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: nil) + + super.init(frame: frame) + + self.backgroundView.clipsToBounds = true + + self.addSubview(self.backgroundView) + self.layer.addSublayer(self.separator) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: FooterComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + let bounds = CGRect(origin: .zero, size: availableSize) + + self.backgroundView.updateColor(color: component.theme.rootController.tabBar.backgroundColor, transition: transition.containedViewLayoutTransition) + self.backgroundView.update(size: bounds.size, transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundView, frame: bounds) + + self.separator.backgroundColor = component.theme.rootController.tabBar.separatorColor.cgColor + transition.setFrame(layer: self.separator, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: UIScreenPixel))) + + let gradientColors = [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent( + SolidRoundedButtonComponent( + title: component.title, + theme: SolidRoundedButtonComponent.Theme( + backgroundColor: .black, + backgroundColors: gradientColors, + foregroundColor: .white + ), + font: .bold, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: true, + iconName: "Premium/BoostChannel", + animationName: nil, + iconPosition: .left, + action: { + component.action() + } + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - 32.0, height: availableSize.height) + ) + + if let view = self.button.view { + if view.superview == nil { + self.addSubview(view) + } + let buttonFrame = CGRect(origin: CGPoint(x: 16.0, y: 8.0), size: buttonSize) + view.frame = buttonFrame + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private struct InternalBoostState: Equatable { + let level: Int32 + let currentLevelBoosts: Int32 + let nextLevelBoosts: Int32? + let boosts: Int32 + + struct DisplayData: Equatable { + let level: Int32 + let boosts: Int32 + let currentLevelBoosts: Int32 + let nextLevelBoosts: Int32? + let myBoostCount: Int32 + } + + func displayData(myBoostCount: Int32, currentMyBoostCount: Int32, replacedBoosts: Int32? = nil) -> DisplayData { + var currentLevel = self.level + var nextLevelBoosts = self.nextLevelBoosts + var currentLevelBoosts = self.currentLevelBoosts + var boosts = self.boosts + if let replacedBoosts { + boosts = max(currentLevelBoosts, boosts - replacedBoosts) + } + + if currentMyBoostCount > 0 && self.boosts == currentLevelBoosts { + currentLevel = max(0, currentLevel - 1) + nextLevelBoosts = currentLevelBoosts + currentLevelBoosts = max(0, currentLevelBoosts - 1) + } + + return DisplayData( + level: currentLevel, + boosts: boosts, + currentLevelBoosts: currentLevelBoosts, + nextLevelBoosts: nextLevelBoosts, + myBoostCount: myBoostCount + ) + } +} diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index ba3b2a1c05..470f4d2f62 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -18,7 +18,7 @@ import BlurredBackgroundComponent import Markdown import TelegramUIPreferences -final class GradientBackgroundComponent: Component { +public final class PremiumGradientBackgroundComponent: Component { public let colors: [UIColor] public init( @@ -27,7 +27,7 @@ final class GradientBackgroundComponent: Component { self.colors = colors } - public static func ==(lhs: GradientBackgroundComponent, rhs: GradientBackgroundComponent) -> Bool { + public static func ==(lhs: PremiumGradientBackgroundComponent, rhs: PremiumGradientBackgroundComponent) -> Bool { if lhs.colors != rhs.colors { return false } @@ -38,7 +38,7 @@ final class GradientBackgroundComponent: Component { private let clipLayer: CALayer private let gradientLayer: CAGradientLayer - private var component: GradientBackgroundComponent? + private var component: PremiumGradientBackgroundComponent? override init(frame: CGRect) { self.clipLayer = CALayer() @@ -58,7 +58,7 @@ final class GradientBackgroundComponent: Component { } - func update(component: GradientBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + func update(component: PremiumGradientBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.clipLayer.frame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: availableSize.height + 10.0)) self.gradientLayer.frame = CGRect(origin: .zero, size: availableSize) @@ -462,7 +462,6 @@ private final class DemoSheetContent: CombinedComponent { let context: AccountContext let subject: PremiumDemoScreen.Subject let source: PremiumDemoScreen.Source - let order: [PremiumPerk] let action: () -> Void let dismiss: () -> Void @@ -470,14 +469,12 @@ private final class DemoSheetContent: CombinedComponent { context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, - order: [PremiumPerk]?, action: @escaping () -> Void, dismiss: @escaping () -> Void ) { self.context = context self.subject = subject self.source = source - self.order = order ?? [.moreUpload, .fasterDownload, .voiceToText, .noAds, .uniqueReactions, .premiumStickers, .animatedEmoji, .advancedChatManagement, .profileBadge, .animatedUserpics, .appIcons, .translation, .stories, .colors, .wallpapers, .messageTags] self.action = action self.dismiss = dismiss } @@ -492,9 +489,6 @@ private final class DemoSheetContent: CombinedComponent { if lhs.source != rhs.source { return false } - if lhs.order != rhs.order { - return false - } return true } @@ -663,7 +657,7 @@ private final class DemoSheetContent: CombinedComponent { static var body: Body { let closeButton = Child(Button.self) - let background = Child(GradientBackgroundComponent.self) + let background = Child(PremiumGradientBackgroundComponent.self) let pager = Child(DemoPagerComponent.self) let button = Child(SolidRoundedButtonComponent.self) let measureText = Child(MultilineTextComponent.self) @@ -679,7 +673,7 @@ private final class DemoSheetContent: CombinedComponent { let sideInset: CGFloat = 16.0 + environment.safeInsets.left let background = background.update( - component: GradientBackgroundComponent(colors: [ + component: PremiumGradientBackgroundComponent(colors: [ UIColor(rgb: 0x0077ff), UIColor(rgb: 0x6b93ff), UIColor(rgb: 0x8878ff), @@ -1000,17 +994,51 @@ private final class DemoSheetContent: CombinedComponent { ) ) ) + availableItems[.lastSeen] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.lastSeen, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["last_seen"], + decoration: .tag + )), + title: strings.Premium_LastSeen, + text: strings.Premium_LastSeenInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.messagePrivacy] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.messagePrivacy, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["message_privacy"], + decoration: .tag + )), + title: strings.Premium_MessagePrivacy, + text: strings.Premium_MessagePrivacyInfo, + textColor: textColor + ) + ) + ) + ) - var items: [DemoPagerComponent.Item] = component.order.compactMap { availableItems[$0] } - let index: Int - switch component.source { - case .intro, .gift: - index = items.firstIndex(where: { (component.subject as AnyHashable) == $0.content.id }) ?? 0 - case .other: - items = items.filter { item in - return item.content.id == (component.subject as AnyHashable) - } - index = 0 + let index: Int = 0 + var items: [DemoPagerComponent.Item] = [] + if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) { + items.append(item.value) + } else { + fatalError() } let pager = pager.update( @@ -1104,6 +1132,10 @@ private final class DemoSheetContent: CombinedComponent { buttonText = strings.Premium_Colors_Proceed case .messageTags: buttonText = strings.Premium_MessageTags_Proceed + case .lastSeen: + buttonText = strings.Premium_LastSeen_Proceed + case .messagePrivacy: + buttonText = strings.Premium_MessagePrivacy_Proceed default: buttonText = strings.Common_OK } @@ -1141,6 +1173,10 @@ private final class DemoSheetContent: CombinedComponent { text = strings.Premium_WallpapersInfo case .messageTags: text = strings.Premium_MessageTagsInfo + case .lastSeen: + text = strings.Premium_LastSeenInfo + case .messagePrivacy: + text = strings.Premium_MessagePrivacyInfo case .doubleLimits, .stories: text = "" } @@ -1248,14 +1284,12 @@ private final class DemoSheetComponent: CombinedComponent { let context: AccountContext let subject: PremiumDemoScreen.Subject let source: PremiumDemoScreen.Source - let order: [PremiumPerk]? let action: () -> Void - init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, action: @escaping () -> Void) { + init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, action: @escaping () -> Void) { self.context = context self.subject = subject self.source = source - self.order = order self.action = action } @@ -1269,10 +1303,6 @@ private final class DemoSheetComponent: CombinedComponent { if lhs.source != rhs.source { return false } - if lhs.order != rhs.order { - return false - } - return true } @@ -1291,7 +1321,6 @@ private final class DemoSheetComponent: CombinedComponent { context: context.component.context, subject: context.component.subject, source: context.component.source, - order: context.component.order, action: context.component.action, dismiss: { animateOut.invoke(Action { _ in @@ -1360,6 +1389,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { case colors case wallpapers case messageTags + case lastSeen + case messagePrivacy } public enum Source: Equatable { @@ -1376,12 +1407,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { return self._ready } - public convenience init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, forceDark: Bool = false, action: @escaping () -> Void) { - self.init(context: context, subject: subject, source: source, order: nil, forceDark: forceDark, action: action) - } - - init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, order: [PremiumPerk]?, forceDark: Bool = false, action: @escaping () -> Void) { - super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, order: order, action: action), navigationBarAppearance: .none, theme: forceDark ? .dark : .default) + public init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, forceDark: Bool = false, action: @escaping () -> Void) { + super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, action: action), navigationBarAppearance: .none, theme: forceDark ? .dark : .default) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) @@ -1421,4 +1448,3 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { } } } - diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index 528853b917..ee7bc25662 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -317,12 +317,6 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { if !component.isCompleted { if let products = component.products { - let gradientColors: [UIColor] = [ - UIColor(rgb: 0x8e77ff), - UIColor(rgb: 0x9a6fff), - UIColor(rgb: 0xb36eee) - ] - let shortestOptionPrice: (Int64, NSDecimalNumber) if let product = products.last { shortestOptionPrice = (Int64(Float(product.storeProduct.priceCurrencyAndAmount.amount) / Float(product.months)), product.storeProduct.priceValue.dividing(by: NSDecimalNumber(value: product.months))) @@ -352,14 +346,20 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { var accessibilitySubtitle = "" var pricePerMonth = environment.strings.Premium_PricePerMonth(product.storeProduct.pricePerMonth(Int(product.months))).string + var labelPrice = pricePerMonth if component.peers.count > 1 { - subtitle = "\(product.storeProduct.price) x \(component.peers.count)" pricePerMonth = product.storeProduct.multipliedPrice(count: component.peers.count) + + subtitle = "" + labelPrice = "\(product.storeProduct.price) x \(component.peers.count)" } else { if discountValue > 0 { subtitle = "**\(defaultPrice)** \(product.price)" accessibilitySubtitle = product.price } + + subtitle = "" + labelPrice = product.price } items.append(SectionGroupComponent.Item( @@ -369,13 +369,13 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { PremiumOptionComponent( title: giftTitle, subtitle: subtitle, - labelPrice: pricePerMonth, + labelPrice: labelPrice, discount: discount, multiple: component.peers.count > 1, selected: product.id == component.selectedProductId, primaryTextColor: textColor, secondaryTextColor: subtitleColor, - accentColor: gradientColors[i], + accentColor: environment.theme.list.itemAccentColor, checkForegroundColor: environment.theme.list.itemCheckColors.foregroundColor, checkBorderColor: environment.theme.list.itemCheckColors.strokeColor ) @@ -424,6 +424,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { UIColor(rgb: 0xef6922), UIColor(rgb: 0xe95a2c), UIColor(rgb: 0xe74e33), + UIColor(rgb: 0xe54937), UIColor(rgb: 0xe3433c), UIColor(rgb: 0xdb374b), UIColor(rgb: 0xcb3e6d), @@ -433,6 +434,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), + UIColor(rgb: 0x6172ff), UIColor(rgb: 0x5b79ff), UIColor(rgb: 0x4492ff), UIColor(rgb: 0x429bd5), @@ -524,6 +526,10 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { demoSubject = .wallpapers case .messageTags: demoSubject = .messageTags + case .lastSeen: + demoSubject = .lastSeen + case .messagePrivacy: + demoSubject = .messagePrivacy } let buttonText: String diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index a62e58ddec..3ecaa6d77f 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -431,6 +431,8 @@ public enum PremiumPerk: CaseIterable { case colors case wallpapers case messageTags + case lastSeen + case messagePrivacy public static var allCases: [PremiumPerk] { return [ @@ -451,7 +453,9 @@ public enum PremiumPerk: CaseIterable { .stories, .colors, .wallpapers, - .messageTags + .messageTags, + .lastSeen, + .messagePrivacy ] } @@ -503,6 +507,10 @@ public enum PremiumPerk: CaseIterable { return "wallpapers" case .messageTags: return "saved_tags" + case .lastSeen: + return "last_seen" + case .messagePrivacy: + return "message_privacy" } } @@ -544,6 +552,10 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_Wallpapers case .messageTags: return strings.Premium_MessageTags + case .lastSeen: + return strings.Premium_LastSeen + case .messagePrivacy: + return strings.Premium_MessagePrivacy } } @@ -585,6 +597,10 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_WallpapersInfo case .messageTags: return strings.Premium_MessageTagsInfo + case .lastSeen: + return strings.Premium_LastSeenInfo + case .messagePrivacy: + return strings.Premium_MessagePrivacyInfo } } @@ -626,6 +642,10 @@ public enum PremiumPerk: CaseIterable { return "Premium/Perk/Wallpapers" case .messageTags: return "Premium/Perk/MessageTags" + case .lastSeen: + return "Premium/Perk/LastSeen" + case .messagePrivacy: + return "Premium/Perk/MessagePrivacy" } } } @@ -636,6 +656,7 @@ struct PremiumIntroConfiguration { .stories, .moreUpload, .doubleLimits, + .lastSeen, .voiceToText, .fasterDownload, .translation, @@ -645,6 +666,7 @@ struct PremiumIntroConfiguration { .colors, .wallpapers, .profileBadge, + .messagePrivacy, .advancedChatManagement, .noAds, .appIcons, @@ -680,11 +702,11 @@ struct PremiumIntroConfiguration { perks = PremiumIntroConfiguration.defaultValue.perks } #if DEBUG - if !perks.contains(.wallpapers) { - perks.append(.wallpapers) + if !perks.contains(.lastSeen) { + perks.append(.lastSeen) } - if !perks.contains(.colors) { - perks.append(.colors) + if !perks.contains(.messagePrivacy) { + perks.append(.messagePrivacy) } if !perks.contains(.messageTags) { perks.append(.messageTags) @@ -845,7 +867,6 @@ final class PremiumOptionComponent: CombinedComponent { transition: context.transition ) - var discountOffset: CGFloat = 0.0 let discountSize: CGSize if !component.discount.isEmpty { let discount = discount.update( @@ -853,7 +874,7 @@ final class PremiumOptionComponent: CombinedComponent { text: .plain( NSAttributedString( string: component.discount, - font: Font.with(size: component.multiple ? 13.0 : 14.0, design: .round, weight: .semibold, traits: []), + font: Font.with(size: 14.0, design: .round, weight: .semibold, traits: []), textColor: .white ) ), @@ -874,13 +895,7 @@ final class PremiumOptionComponent: CombinedComponent { transition: context.transition ) - let discountPosition: CGPoint - if component.multiple { - discountOffset = discountSize.width + 6.0 - discountPosition = CGPoint(x: insets.left + discountSize.width / 2.0, y: insets.top + title.size.height + discountSize.height / 2.0) - } else { - discountPosition = CGPoint(x: insets.left + title.size.width + 6.0 + discountSize.width / 2.0, y: insets.top + title.size.height / 2.0) - } + let discountPosition = CGPoint(x: insets.left + title.size.width + 6.0 + discountSize.width / 2.0, y: insets.top + title.size.height / 2.0) context.add(discountBackground .position(discountPosition) @@ -919,7 +934,7 @@ final class PremiumOptionComponent: CombinedComponent { transition: context.transition ) context.add(subtitle - .position(CGPoint(x: insets.left + subtitle.size.width / 2.0 + discountOffset, y: insets.top + title.size.height + spacing + subtitle.size.height / 2.0)) + .position(CGPoint(x: insets.left + subtitle.size.width / 2.0, y: insets.top + title.size.height + spacing + subtitle.size.height / 2.0)) ) subtitleSize = subtitle.size @@ -1650,8 +1665,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { ApplicationSpecificNotice.dismissedPremiumAppIconsBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedPremiumWallpapersBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedPremiumColorsBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedMessageTagsBadge(accountManager: context.sharedContext.accountManager) - ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge in + ApplicationSpecificNotice.dismissedMessageTagsBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedLastSeenBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedMessagePrivacyBadge(accountManager: context.sharedContext.accountManager) + ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge, dismissedLastSeenBadge, dismissedMessagePrivacyBadge in guard let self else { return } @@ -1665,6 +1682,12 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if !dismissedMessageTagsBadge { newPerks.append(PremiumPerk.messageTags.identifier) } + if !dismissedLastSeenBadge { + newPerks.append(PremiumPerk.lastSeen.identifier) + } + if !dismissedMessagePrivacyBadge { + newPerks.append(PremiumPerk.messagePrivacy.identifier) + } self.newPerks = newPerks self.updated() }) @@ -1839,6 +1862,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0xef6922), UIColor(rgb: 0xe95a2c), UIColor(rgb: 0xe74e33), + UIColor(rgb: 0xe54937), UIColor(rgb: 0xe3433c), UIColor(rgb: 0xdb374b), UIColor(rgb: 0xcb3e6d), @@ -1848,6 +1872,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), + UIColor(rgb: 0x6172ff), UIColor(rgb: 0x5b79ff), UIColor(rgb: 0x4492ff), UIColor(rgb: 0x429bd5), @@ -1865,11 +1890,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let layoutOptions = { if let products = state.products, products.count > 1, state.isPremium == false || (!context.component.justBought && state.canUpgrade) { var optionsItems: [SectionGroupComponent.Item] = [] - let gradientColors: [UIColor] = [ - UIColor(rgb: 0x8e77ff), - UIColor(rgb: 0x9a6fff), - UIColor(rgb: 0xb36eee) - ] let shortestOptionPrice: (Int64, NSDecimalNumber) if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) { @@ -1938,7 +1958,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { selected: !product.isCurrent && product.id == state.selectedProductId, primaryTextColor: textColor, secondaryTextColor: subtitleColor, - accentColor: gradientColors[i], + accentColor: environment.theme.list.itemAccentColor, checkForegroundColor: environment.theme.list.itemCheckColors.foregroundColor, checkBorderColor: environment.theme.list.itemCheckColors.strokeColor ) @@ -2071,6 +2091,12 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { case .messageTags: demoSubject = .messageTags let _ = ApplicationSpecificNotice.setDismissedMessageTagsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() + case .lastSeen: + demoSubject = .lastSeen + let _ = ApplicationSpecificNotice.setDismissedLastSeenBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() + case .messagePrivacy: + demoSubject = .messagePrivacy + let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() } let isPremium = state?.isPremium == true diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 829c0e8e0f..2b5ae9062f 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -323,6 +323,7 @@ public class PremiumLimitDisplayComponent: Component { self.badgeIcon.image = component.badgeIconName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) } self.badgeIcon.tintColor = component.activeTitleColor self.badgeView.isHidden = self.badgeIcon.image == nil + self.badgeLabel.color = component.activeTitleColor let lineHeight: CGFloat = 30.0 let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - lineHeight), size: CGSize(width: size.width, height: lineHeight)) @@ -1033,7 +1034,7 @@ private final class LimitSheetContent: CombinedComponent { string = component.count >= premiumLimit ? strings.Premium_MaxSavedPinsFinalText("\(premiumLimit)").string : strings.Premium_MaxSavedPinsText("\(limit)", "\(premiumLimit)").string defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" - badgePosition = max(0.35, min(0.85, CGFloat(component.count) / CGFloat(premiumLimit))) + badgePosition = max(0.35, min(0.65, CGFloat(component.count) / CGFloat(premiumLimit))) badgeGraphPosition = badgePosition buttonAnimationName = nil @@ -1126,6 +1127,11 @@ private final class LimitSheetContent: CombinedComponent { } buttonAnimationName = nil case let .storiesChannelBoost(peer, boostSubject, isCurrent, level, currentLevelBoosts, nextLevelBoosts, link, myBoostCount, canBoostAgain): + var isGroup = false + if case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + if link == nil, !isCurrent, state.initialized { peerShortcutChild = peerShortcut.update( component: Button( @@ -1133,8 +1139,7 @@ private final class LimitSheetContent: CombinedComponent { PeerShortcutComponent( context: component.context, theme: environment.theme, - peer: peer, - badge: myBoostCount > 0 ? "\(myBoostCount)" : nil + peer: peer ) ), action: { @@ -1192,6 +1197,7 @@ private final class LimitSheetContent: CombinedComponent { remaining = nextLevelBoosts - component.count } + if let _ = link { if let remaining { let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1) @@ -1201,15 +1207,15 @@ private final class LimitSheetContent: CombinedComponent { case .stories: if level == 0 { titleText = strings.ChannelBoost_EnableStories - string = strings.ChannelBoost_EnableStoriesText(valueString).string + string = isGroup ? strings.GroupBoost_EnableStoriesText(valueString).string : strings.ChannelBoost_EnableStoriesText(valueString).string } else { titleText = strings.ChannelBoost_IncreaseLimit - string = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string + string = isGroup ? strings.GroupBoost_IncreaseLimitText(valueString, storiesString).string : strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string } case let .nameColors(colors): titleText = strings.ChannelBoost_EnableColors - let colorLevel = requiredBoostSubjectLevel(subject: .nameColors(colors: colors), context: component.context, configuration: premiumConfiguration) + let colorLevel = requiredBoostSubjectLevel(subject: .nameColors(colors: colors), group: false, context: component.context, configuration: premiumConfiguration) string = strings.ChannelBoost_EnableColorsLevelText("\(colorLevel)").string case let .channelReactions(reactionCount): @@ -1884,17 +1890,15 @@ public class PremiumLimitScreen: ViewControllerComponentContainer { } } -private final class PeerShortcutComponent: Component { +final class PeerShortcutComponent: Component { let context: AccountContext let theme: PresentationTheme let peer: EnginePeer - let badge: String? - init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer, badge: String?) { + init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) { self.context = context self.theme = theme self.peer = peer - self.badge = badge } static func ==(lhs: PeerShortcutComponent, rhs: PeerShortcutComponent) -> Bool { @@ -1907,9 +1911,6 @@ private final class PeerShortcutComponent: Component { if lhs.peer != rhs.peer { return false } - if lhs.badge != rhs.badge { - return false - } return true } diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index e743fe4bfd..0ca02854bf 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -363,7 +363,7 @@ public class PremiumLimitsListScreen: ViewController { let backgroundSize = self.backgroundView.update( transition: .immediate, component: AnyComponent( - GradientBackgroundComponent(colors: [ + PremiumGradientBackgroundComponent(colors: [ UIColor(rgb: 0x0077ff), UIColor(rgb: 0x6b93ff), UIColor(rgb: 0x8878ff), @@ -761,7 +761,45 @@ public class PremiumLimitsListScreen: ViewController { ) ) ) - + availableItems[.lastSeen] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.lastSeen, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + model: .island, + videoFile: configuration.videos["last_seen"], + decoration: .badgeStars + )), + title: strings.Premium_LastSeen, + text: strings.Premium_LastSeenInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.messagePrivacy] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.messagePrivacy, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + model: .island, + videoFile: configuration.videos["message_privacy"], + decoration: .swirlStars + )), + title: strings.Premium_MessagePrivacy, + text: strings.Premium_MessagePrivacyInfo, + textColor: textColor + ) + ) + ) + ) + if let order = controller.order { var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] } let initialIndex: Int diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift index 3821086a4c..d03b7a9752 100644 --- a/submodules/PremiumUI/Sources/StoriesPageComponent.swift +++ b/submodules/PremiumUI/Sources/StoriesPageComponent.swift @@ -582,7 +582,6 @@ final class StoriesPageComponent: CombinedComponent { let topPanel = Child(BlurredBackgroundComponent.self) let topSeparator = Child(Rectangle.self) let title = Child(MultilineTextComponent.self) - let secondaryTitle = Child(MultilineTextComponent.self) let resetScroll = ActionSlot() @@ -664,17 +663,6 @@ final class StoriesPageComponent: CombinedComponent { availableSize: context.availableSize, transition: context.transition ) - - let secondaryTitle = secondaryTitle.update( - component: MultilineTextComponent( - text: .plain(NSAttributedString(string: strings.Premium_Stories_AdditionalTitle, font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)), - horizontalAlignment: .center, - truncationType: .end, - maximumNumberOfLines: 1 - ), - availableSize: context.availableSize, - transition: context.transition - ) let topPanelAlpha: CGFloat if state.topContentOffset > 78.0 { @@ -699,23 +687,9 @@ final class StoriesPageComponent: CombinedComponent { let titleOriginY: CGFloat = titleBottomOriginY + fraction * titleOriginDelta let titleScale = 1.0 - max(0.0, fraction * 0.2) - let titleAlpha: CGFloat - if fraction > 0.78 { - titleAlpha = max(0.0, 1.0 - (fraction - 0.78) / 0.16) - } else { - titleAlpha = 1.0 - } - let secondaryTitleAlpha: CGFloat = 1.0 - titleAlpha - context.add(title .position(CGPoint(x: context.availableSize.width / 2.0, y: titleOriginY)) .scale(titleScale) - .opacity(titleAlpha) - ) - - context.add(secondaryTitle - .position(CGPoint(x: context.availableSize.width / 2.0, y: titleOriginY)) - .opacity(secondaryTitleAlpha) ) return scroll.size diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 2cf408ee88..fff04dc3e8 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -1722,6 +1722,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1770,6 +1771,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { groupId: AnyHashable(info.id), title: info.title, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1828,6 +1830,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1859,6 +1862,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Disposable.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Disposable.swift index 3d742fcb36..d2d901bbdf 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Disposable.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Disposable.swift @@ -190,6 +190,12 @@ public final class DisposableSet : Disposable { pthread_mutex_unlock(&self.lock) } + public func removeLast() { + pthread_mutex_lock(&self.lock) + self.disposables.removeLast() + pthread_mutex_unlock(&self.lock) + } + public func dispose() { var disposables: [Disposable] = [] pthread_mutex_lock(&self.lock) diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index 72c5e8e88f..b0264ed24f 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -60,6 +60,7 @@ private final class InstalledStickerPacksControllerArguments { } private enum InstalledStickerPacksSection: Int32 { + case info case categories case settings case stickers @@ -83,6 +84,7 @@ private enum InstalledStickerPacksEntryId: Hashable { } private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { + case info(PresentationTheme, String) case suggestOptions(PresentationTheme, String, String) case largeEmoji(PresentationTheme, String, Bool) case trending(PresentationTheme, String, Int32) @@ -100,6 +102,8 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { var section: ItemListSectionId { switch self { + case .info: + return InstalledStickerPacksSection.info.rawValue case .trending, .masks, .emoji, .quickReaction, .archived: return InstalledStickerPacksSection.categories.rawValue case .suggestOptions, .largeEmoji, .suggestAnimatedEmoji, .suggestAnimatedEmojiInfo, .packOrder, .packOrderInfo: @@ -111,6 +115,8 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { var stableId: InstalledStickerPacksEntryId { switch self { + case .info: + return .index(-1) case .trending: return .index(0) case .archived: @@ -144,6 +150,12 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { static func ==(lhs: InstalledStickerPacksEntry, rhs: InstalledStickerPacksEntry) -> Bool { switch lhs { + case let .info(lhsTheme, lhsText): + if case let .info(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } case let .suggestOptions(lhsTheme, lhsText, lhsValue): if case let .suggestOptions(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true @@ -263,86 +275,93 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { static func <(lhs: InstalledStickerPacksEntry, rhs: InstalledStickerPacksEntry) -> Bool { switch lhs { + case .info: + switch rhs { + case .info: + return false + default: + return true + } case .trending: switch rhs { - case .trending: + case .info, .trending: return false default: return true } case .archived: switch rhs { - case .trending, .archived: + case .info, .trending, .archived: return false default: return true } case .masks: switch rhs { - case .trending, .archived, .masks: + case .info, .trending, .archived, .masks: return false default: return true } case .emoji: switch rhs { - case .trending, .archived, .masks, .emoji: + case .info, .trending, .archived, .masks, .emoji: return false default: return true } case .quickReaction: switch rhs { - case .trending, .archived, .masks, .emoji, .quickReaction: + case .info, .trending, .archived, .masks, .emoji, .quickReaction: return false default: return true } case .suggestOptions: switch rhs { - case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions: + case .info, .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions: return false default: return true } case .largeEmoji: switch rhs { - case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji: + case .info, .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji: return false default: return true } case .packOrder: switch rhs { - case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder: + case .info, .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder: return false default: return true } case .packOrderInfo: switch rhs { - case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo: + case .info, .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo: return false default: return true } case .suggestAnimatedEmoji: switch rhs { - case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji: + case .info, .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji: return false default: return true } case .suggestAnimatedEmojiInfo: switch rhs { - case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji, .suggestAnimatedEmojiInfo: + case .info, .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji, .suggestAnimatedEmojiInfo: return false default: return true } case .packsTitle: switch rhs { - case .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji, .suggestAnimatedEmojiInfo, .packsTitle: + case .info, .trending, .archived, .masks, .emoji, .quickReaction, .suggestOptions, .largeEmoji, .packOrder, .packOrderInfo, .suggestAnimatedEmoji, .suggestAnimatedEmojiInfo, .packsTitle: return false default: return true @@ -369,6 +388,8 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! InstalledStickerPacksControllerArguments switch self { + case let .info(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .suggestOptions(_, text, value): return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openSuggestOptions() @@ -600,12 +621,12 @@ private func installedStickerPacksControllerEntries(context: AccountContext, pre var markdownString: String switch mode { - case .general, .modal: - markdownString = presentationData.strings.StickerPacksSettings_ManagingHelp - case .masks: - markdownString = presentationData.strings.MaskStickerSettings_Info - case .emoji: - markdownString = presentationData.strings.EmojiStickerSettings_Info + case .general, .modal: + markdownString = presentationData.strings.StickerPacksSettings_ManagingHelp + case .masks: + markdownString = presentationData.strings.MaskStickerSettings_Info + case .emoji: + markdownString = presentationData.strings.EmojiStickerSettings_Info } let entities = generateTextEntities(markdownString, enabledTypes: [.mention]) if let entity = entities.first { @@ -618,7 +639,12 @@ private func installedStickerPacksControllerEntries(context: AccountContext, pre } public func installedStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, archivedPacks: [ArchivedStickerPackItem]? = nil, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void = { _ in }, focusOnItemTag: InstalledStickerPacksEntryTag? = nil, forceTheme: PresentationTheme? = nil) -> ViewController { - let initialState = InstalledStickerPacksControllerState().withUpdatedEditing(mode == .modal).withUpdatedSelectedPackIds(mode == .modal ? Set() : nil) + var initialEditing = false + if case .modal = mode { + initialEditing = true + } + + let initialState = InstalledStickerPacksControllerState().withUpdatedEditing(initialEditing).withUpdatedSelectedPackIds(initialEditing ? Set() : nil) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) let updateState: ((InstalledStickerPacksControllerState) -> InstalledStickerPacksControllerState) -> Void = { f in diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 3d329a577c..64f4d74eaf 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -286,6 +286,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, + mediaDraftContentType: nil, inputActivities: hasInputActivity ? [(author, .typingText)] : [], promoInfo: nil, ignoreUnreadBadge: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 4e664b9040..10d7b69502 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -433,6 +433,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, + mediaDraftContentType: nil, inputActivities: hasInputActivity ? [(author, .typingText)] : [], promoInfo: nil, ignoreUnreadBadge: false, diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 79c09bffc9..673cb12c01 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -1460,7 +1460,8 @@ private func threadList(accountPeerId: EnginePeer.Id, postbox: Postbox, peerId: autoremoveTimeout: nil, storyStats: nil, displayAsTopicList: false, - isPremiumRequiredToMessage: false + isPremiumRequiredToMessage: false, + mediaDraftContentType: nil )) } diff --git a/submodules/StatisticsUI/BUILD b/submodules/StatisticsUI/BUILD index 4246307631..6bf260d9c3 100644 --- a/submodules/StatisticsUI/BUILD +++ b/submodules/StatisticsUI/BUILD @@ -38,6 +38,7 @@ swift_library( "//submodules/ShareController:ShareController", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", + "//submodules/TelegramUI/Components/PlainButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/StatisticsUI/Sources/BackButton.swift b/submodules/StatisticsUI/Sources/BackButton.swift new file mode 100644 index 0000000000..271cae3d65 --- /dev/null +++ b/submodules/StatisticsUI/Sources/BackButton.swift @@ -0,0 +1,268 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import ContextUI +import TelegramPresentationData +import Display + +enum PeerInfoHeaderNavigationButtonKey { + case back + case edit + case done + case cancel + case select + case selectionDone + case search + case editPhoto + case editVideo + case more + case qrCode + case moreToSearch + case postStory +} + +final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { + let containerNode: ContextControllerSourceNode + let contextSourceNode: ContextReferenceContentNode + private let textNode: ImmediateTextNode + private let iconNode: ASImageNode + private let backIconLayer: SimpleShapeLayer + private let backgroundNode: NavigationBackgroundNode + + private var key: PeerInfoHeaderNavigationButtonKey? + + private var contentsColor: UIColor = .white + private var canBeExpanded: Bool = false + + var action: ((ASDisplayNode, ContextGesture?) -> Void)? + + init() { + self.contextSourceNode = ContextReferenceContentNode() + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + + self.textNode = ImmediateTextNode() + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + + self.backIconLayer = SimpleShapeLayer() + self.backIconLayer.lineWidth = 3.0 + self.backIconLayer.lineCap = .round + self.backIconLayer.lineJoin = .round + self.backIconLayer.strokeColor = UIColor.white.cgColor + self.backIconLayer.fillColor = nil + self.backIconLayer.isHidden = true + self.backIconLayer.path = try? convertSvgPath("M10.5,2 L1.5,11 L10.5,20 ") + + self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true) + + super.init(pointerStyle: .insetRectangle(-8.0, 2.0)) + + self.isAccessibilityElement = true + self.accessibilityTraits = .button + + self.containerNode.addSubnode(self.contextSourceNode) + self.contextSourceNode.addSubnode(self.backgroundNode) + self.contextSourceNode.addSubnode(self.textNode) + self.contextSourceNode.addSubnode(self.iconNode) + self.contextSourceNode.layer.addSublayer(self.backIconLayer) + + self.addSubnode(self.containerNode) + + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self else { + return + } + strongSelf.action?(strongSelf.contextSourceNode, gesture) + } + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc private func pressed() { + self.action?(self.contextSourceNode, nil) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + var boundingRect = self.bounds + if self.textNode.alpha != 0.0 { + boundingRect = boundingRect.union(self.textNode.frame) + } + boundingRect = boundingRect.insetBy(dx: -8.0, dy: -4.0) + if boundingRect.contains(point) { + return super.hitTest(self.bounds.center, with: event) + } else { + return nil + } + } + + func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) { + self.contentsColor = contentsColor + self.canBeExpanded = canBeExpanded + + self.backgroundNode.updateColor(color: backgroundColor, transition: transition) + + transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) + transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) + transition.updateStrokeColor(layer: self.backIconLayer, strokeColor: self.contentsColor) + + switch self.key { + case .back: + transition.updateAlpha(layer: self.textNode.layer, alpha: canBeExpanded ? 1.0 : 0.0) + transition.updateTransformScale(node: self.textNode, scale: canBeExpanded ? 1.0 : 0.001) + + var iconTransform = CATransform3DIdentity + iconTransform = CATransform3DScale(iconTransform, canBeExpanded ? 1.0 : 0.8, canBeExpanded ? 1.0 : 0.8, 1.0) + iconTransform = CATransform3DTranslate(iconTransform, canBeExpanded ? -7.0 : 0.0, 0.0, 0.0) + transition.updateTransform(node: self.iconNode, transform: CATransform3DGetAffineTransform(iconTransform)) + + transition.updateTransform(layer: self.backIconLayer, transform: CATransform3DGetAffineTransform(iconTransform)) + transition.updateLineWidth(layer: self.backIconLayer, lineWidth: canBeExpanded ? 3.0 : 2.075) + default: + break + } + } + + func update(key: PeerInfoHeaderNavigationButtonKey, presentationData: PresentationData, height: CGFloat) -> CGSize { + let transition: ContainedViewLayoutTransition = .immediate + + var iconOffset = CGPoint() + switch key { + case .back: + iconOffset = CGPoint(x: -1.0, y: 0.0) + default: + break + } + + let textSize: CGSize + if self.key != key { + self.key = key + + let text: String + var accessibilityText: String + var icon: UIImage? + var isBold = false + var isGestureEnabled = false + switch key { + case .back: + text = presentationData.strings.Common_Back + accessibilityText = presentationData.strings.Common_Back + icon = NavigationBar.backArrowImage(color: .white) + case .edit: + text = presentationData.strings.Common_Edit + accessibilityText = text + case .cancel: + text = presentationData.strings.Common_Cancel + accessibilityText = text + isBold = false + case .done, .selectionDone: + text = presentationData.strings.Common_Done + accessibilityText = text + isBold = true + case .select: + text = presentationData.strings.Common_Select + accessibilityText = text + case .search: + text = "" + accessibilityText = presentationData.strings.Common_Search + icon = nil// PresentationResourcesRootController.navigationCompactSearchIcon(presentationData.theme) + case .editPhoto: + text = presentationData.strings.Settings_EditPhoto + accessibilityText = text + case .editVideo: + text = presentationData.strings.Settings_EditVideo + accessibilityText = text + case .more: + text = "" + accessibilityText = presentationData.strings.Common_More + icon = nil// PresentationResourcesRootController.navigationMoreCircledIcon(presentationData.theme) + isGestureEnabled = true + case .qrCode: + text = "" + accessibilityText = presentationData.strings.PeerInfo_QRCode_Title + icon = PresentationResourcesRootController.navigationQrCodeIcon(presentationData.theme) + case .moreToSearch: + text = "" + accessibilityText = "" + case .postStory: + text = "" + accessibilityText = presentationData.strings.Story_Privacy_PostStory + icon = PresentationResourcesRootController.navigationPostStoryIcon(presentationData.theme) + } + self.accessibilityLabel = accessibilityText + self.containerNode.isGestureEnabled = isGestureEnabled + + let font: UIFont = isBold ? Font.semibold(17.0) : Font.regular(17.0) + + self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: .white) + transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) + self.iconNode.image = icon + transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) + + self.iconNode.isHidden = false + + textSize = self.textNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + } else { + textSize = self.textNode.bounds.size + } + + let inset: CGFloat = 0.0 + var textInset: CGFloat = 0.0 + switch key { + case .back: + textInset += 11.0 + default: + break + } + + let resultSize: CGSize + + let textFrame = CGRect(origin: CGPoint(x: inset + textInset, y: floor((height - textSize.height) / 2.0)), size: textSize) + self.textNode.position = textFrame.center + self.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + + if let image = self.iconNode.image { + let iconFrame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size).offsetBy(dx: iconOffset.x, dy: iconOffset.y) + self.iconNode.position = iconFrame.center + self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) + + if case .back = key { + self.backIconLayer.position = iconFrame.center + self.backIconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) + + self.iconNode.isHidden = true + self.backIconLayer.isHidden = false + } else { + self.iconNode.isHidden = false + self.backIconLayer.isHidden = true + } + + let size = CGSize(width: image.size.width + inset * 2.0, height: height) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) + self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) + resultSize = size + } else { + let size = CGSize(width: textSize.width + inset * 2.0, height: height) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) + self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) + resultSize = size + } + + let diameter: CGFloat = 32.0 + let backgroundWidth: CGFloat + if self.iconNode.image != nil { + backgroundWidth = diameter + } else { + backgroundWidth = max(diameter, resultSize.width + 12.0 * 2.0) + } + let backgroundFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - backgroundWidth) * 0.5), y: floor((resultSize.height - diameter) * 0.5)), size: CGSize(width: backgroundWidth, height: diameter)) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: diameter * 0.5, transition: transition) + + self.hitTestSlop = UIEdgeInsets(top: -2.0, left: -12.0, bottom: -2.0, right: -12.0) + + return resultSize + } +} diff --git a/submodules/StatisticsUI/Sources/BoostHeaderItem.swift b/submodules/StatisticsUI/Sources/BoostHeaderItem.swift new file mode 100644 index 0000000000..cf15172bd6 --- /dev/null +++ b/submodules/StatisticsUI/Sources/BoostHeaderItem.swift @@ -0,0 +1,514 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import ItemListUI +import PresentationDataUtils +import Markdown +import ComponentFlow +import PremiumUI +import MultilineTextComponent +import BundleIconComponent +import PlainButtonComponent +import AccountContext + +final class BoostHeaderItem: ItemListControllerHeaderItem { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let status: ChannelBoostStatus + let title: String + let text: String + let openBoost: () -> Void + let createGiveaway: () -> Void + let openFeatures: () -> Void + let back: () -> Void + + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, status: ChannelBoostStatus, title: String, text: String, openBoost: @escaping () -> Void, createGiveaway: @escaping () -> Void, openFeatures: @escaping () -> Void, back: @escaping () -> Void) { + self.context = context + self.theme = theme + self.strings = strings + self.status = status + self.title = title + self.text = text + self.openBoost = openBoost + self.createGiveaway = createGiveaway + self.openFeatures = openFeatures + self.back = back + } + + func isEqual(to: ItemListControllerHeaderItem) -> Bool { + if let item = to as? BoostHeaderItem { + return self.theme === item.theme && self.title == item.title && self.text == item.text + } else { + return false + } + } + + func node(current: ItemListControllerHeaderItemNode?) -> ItemListControllerHeaderItemNode { + if let current = current as? BoostHeaderItemNode { + current.item = self + return current + } else { + return BoostHeaderItemNode(item: self) + } + } +} + +private let titleFont = Font.semibold(17.0) + +final class BoostHeaderItemNode: ItemListControllerHeaderItemNode { + private let backgroundNode: NavigationBackgroundNode + private let separatorNode: ASDisplayNode + private let whiteTitleNode: ImmediateTextNode + private let titleNode: ImmediateTextNode + private let backButton = PeerInfoHeaderNavigationButton() + + private var hostView: ComponentHostView? + + private var component: AnyComponent? + private var validLayout: ContainerViewLayout? + + fileprivate var item: BoostHeaderItem { + didSet { + self.updateItem() + if let layout = self.validLayout { + let _ = self.updateLayout(layout: layout, transition: .immediate) + } + } + } + + init(item: BoostHeaderItem) { + self.item = item + + self.backgroundNode = NavigationBackgroundNode(color: item.theme.rootController.navigationBar.blurredBackgroundColor) + self.backgroundNode.alpha = 0.0 + self.separatorNode = ASDisplayNode() + self.separatorNode.alpha = 0.0 + + self.whiteTitleNode = ImmediateTextNode() + self.whiteTitleNode.isUserInteractionEnabled = false + self.whiteTitleNode.contentMode = .left + self.whiteTitleNode.contentsScale = UIScreen.main.scale + + self.titleNode = ImmediateTextNode() + self.titleNode.alpha = 0.0 + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.separatorNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.whiteTitleNode) + self.addSubnode(self.backButton) + + self.updateItem() + + self.backButton.action = { [weak self] _, _ in + if let self { + self.item.back() + } + } + } + + override func didLoad() { + super.didLoad() + + let hostView = ComponentHostView() + self.hostView = hostView + self.view.insertSubview(hostView, at: 0) + + if let layout = self.validLayout, let component = self.component { + let navigationBarHeight: CGFloat = 44.0 + let statusBarHeight = layout.statusBarHeight ?? 0.0 + let containerSize = CGSize(width: layout.size.width, height: navigationBarHeight + statusBarHeight + 266.0) + + let size = hostView.update( + transition: .immediate, + component: component, + environment: {}, + containerSize: containerSize + ) + hostView.frame = CGRect(origin: CGPoint(x: 0.0, y: -self.contentOffset), size: size) + } + } + + func updateItem() { + self.backgroundNode.updateColor(color: self.item.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) + self.separatorNode.backgroundColor = self.item.theme.rootController.navigationBar.separatorColor + + self.titleNode.attributedText = NSAttributedString(string: self.item.title, font: titleFont, textColor: self.item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) + self.whiteTitleNode.attributedText = NSAttributedString(string: self.item.title, font: titleFont, textColor: .white, paragraphAlignment: .center) + } + + private var contentOffset: CGFloat = 0.0 + override func updateContentOffset(_ contentOffset: CGFloat, transition: ContainedViewLayoutTransition) { + guard let layout = self.validLayout else { + return + } + self.contentOffset = contentOffset + + let topPanelAlpha = min(20.0, max(0.0, contentOffset - 44.0)) / 20.0 + transition.updateAlpha(node: self.backgroundNode, alpha: topPanelAlpha) + transition.updateAlpha(node: self.separatorNode, alpha: topPanelAlpha) + + transition.updateAlpha(node: self.titleNode, alpha: topPanelAlpha) + transition.updateAlpha(node: self.whiteTitleNode, alpha: 1.0 - topPanelAlpha) + + let scrolledUp = topPanelAlpha < 0.5 + self.backButton.updateContentsColor(backgroundColor: scrolledUp ? UIColor(white: 1.0, alpha: 0.2) : .clear, contentsColor: scrolledUp ? .white : self.item.theme.rootController.navigationBar.accentTextColor, canBeExpanded: !scrolledUp, transition: .animated(duration: 0.2, curve: .easeInOut)) + + if let hostView = self.hostView { + hostView.center = CGPoint(x: layout.size.width / 2.0, y: hostView.frame.height / 2.0 - contentOffset) + } + } + + override func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat { + let isFirstTime = self.validLayout == nil + let leftInset: CGFloat = 24.0 + + let navigationBarHeight: CGFloat = 44.0 + let statusBarHeight = layout.statusBarHeight ?? 0.0 + + let constrainedSize = CGSize(width: layout.size.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude) + let titleSize = self.titleNode.updateLayout(constrainedSize) + let _ = self.whiteTitleNode.updateLayout(constrainedSize) + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: statusBarHeight + navigationBarHeight))) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusBarHeight + navigationBarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + + self.backgroundNode.update(size: CGSize(width: layout.size.width, height: statusBarHeight + navigationBarHeight), transition: transition) + + let component = AnyComponent(BoostHeaderComponent( + strings: self.item.strings, + text: self.item.text, + status: self.item.status, + openBoost: self.item.openBoost, + createGiveaway: self.item.createGiveaway, + openFeatures: self.item.openFeatures + )) + let containerSize = CGSize(width: layout.size.width, height: navigationBarHeight + statusBarHeight + 266.0) + + if let hostView = self.hostView { + let size = hostView.update( + transition: .immediate, + component: component, + environment: {}, + containerSize: containerSize + ) + hostView.frame = CGRect(origin: CGPoint(x: 0.0, y: -self.contentOffset), size: size) + } + + self.titleNode.bounds = CGRect(origin: .zero, size: titleSize) + self.titleNode.position = CGPoint(x: layout.size.width / 2.0, y: statusBarHeight + navigationBarHeight / 2.0) + + self.whiteTitleNode.bounds = self.titleNode.bounds + self.whiteTitleNode.position = self.titleNode.position + + let backSize = self.backButton.update(key: .back, presentationData: self.item.context.sharedContext.currentPresentationData.with { $0 }, height: 44.0) + self.backButton.frame = CGRect(origin: CGPoint(x: 16.0, y: 54.0), size: backSize) + + self.component = component + self.validLayout = layout + + if isFirstTime { + self.backButton.updateContentsColor(backgroundColor: UIColor(white: 1.0, alpha: 0.2), contentsColor: .white, canBeExpanded: false, transition: .immediate) + } + + return containerSize.height + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if let hostView = self.hostView, hostView.frame.contains(point) { + return true + } else { + return super.point(inside: point, with: event) + } + } +} + +private final class BoostHeaderComponent: CombinedComponent { + let strings: PresentationStrings + let text: String + let status: ChannelBoostStatus + let openBoost: () -> Void + let createGiveaway: () -> Void + let openFeatures: () -> Void + + public init( + strings: PresentationStrings, + text: String, + status: ChannelBoostStatus, + openBoost: @escaping () -> Void, + createGiveaway: @escaping () -> Void, + openFeatures: @escaping () -> Void + ) { + self.strings = strings + self.text = text + self.status = status + self.openBoost = openBoost + self.createGiveaway = createGiveaway + self.openFeatures = openFeatures + } + + public static func ==(lhs: BoostHeaderComponent, rhs: BoostHeaderComponent) -> Bool { + if lhs.strings !== rhs.strings { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.status != rhs.status { + return false + } + return true + } + + public static var body: Body { + let background = Child(PremiumGradientBackgroundComponent.self) + let stars = Child(BoostHeaderBackgroundComponent.self) + let progress = Child(PremiumLimitDisplayComponent.self) + let text = Child(MultilineTextComponent.self) + + let boostButton = Child(PlainButtonComponent.self) + let giveawayButton = Child(PlainButtonComponent.self) + let featuresButton = Child(PlainButtonComponent.self) + + return { context in + let size = context.availableSize + let sideInset: CGFloat = 16.0 + let component = context.component + + let background = background.update( + component: PremiumGradientBackgroundComponent( + colors: [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] + ), + availableSize: size, + transition: context.transition + ) + context.add(background + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + ) + + let stars = stars.update( + component: BoostHeaderBackgroundComponent( + isVisible: true, + hasIdleAnimations: true + ), + availableSize: size, + transition: context.transition + ) + context.add(stars + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 + 20.0)) + ) + + let level = component.status.level + let position: CGFloat + if let nextLevelBoosts = component.status.nextLevelBoosts { + position = CGFloat(component.status.boosts - component.status.currentLevelBoosts) / CGFloat(nextLevelBoosts - component.status.currentLevelBoosts) + } else { + position = 1.0 + } + + let inactiveText = component.strings.ChannelBoost_Level("\(level)").string + let activeText = component.strings.ChannelBoost_Level("\(level + 1)").string + + let progress = progress.update( + component: PremiumLimitDisplayComponent( + inactiveColor: UIColor.white.withAlphaComponent(0.2), + activeColors: [.white, .white], + inactiveTitle: inactiveText, + inactiveValue: "", + inactiveTitleColor: .white, + activeTitle: "", + activeValue: activeText, + activeTitleColor: UIColor(rgb: 0x6f8fff), + badgeIconName: "Premium/Boost", + badgeText: "\(component.status.boosts)", + badgePosition: position, + badgeGraphPosition: position, + invertProgress: true, + isPremiumDisabled: false + ), + availableSize: CGSize(width: size.width - sideInset * 2.0, height: size.height), + transition: context.transition + ) + + context.add(progress + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 - 36.0)) + ) + + let font = Font.regular(15.0) + let boldFont = Font.semibold(15.0) + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: font, textColor: .white), bold: MarkdownAttributeSet(font: boldFont, textColor: .white), link: MarkdownAttributeSet(font: font, textColor: .white), linkAttribute: { _ in return nil}) + + let text = text.update( + component: MultilineTextComponent( + text: .markdown(text: component.text, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: size.width - sideInset * 3.0, height: size.height), + transition: context.transition + ) + context.add(text + .position(CGPoint(x: size.width / 2.0, y: size.height - 114.0)) + ) + + let minButtonWidth: CGFloat = 112.0 +// let buttonSpacing = (size.width - sideInset * 2.0 - minButtonWidth * 3.0) / 2.0 + let buttonHeight: CGFloat = 58.0 + + let boostButton = boostButton.update( + component: PlainButtonComponent( + content: AnyComponent( + BoostButtonComponent( + iconName: "Premium/Boosts/Boost", + title: "boost" + ) + ), + effectAlignment: .center, + action: { + component.openBoost() + } + ), + availableSize: CGSize(width: minButtonWidth, height: buttonHeight), + transition: context.transition + ) + context.add(boostButton + .position(CGPoint(x: sideInset + minButtonWidth / 2.0, y: size.height - 45.0)) + ) + + let giveawayButton = giveawayButton.update( + component: PlainButtonComponent( + content: AnyComponent( + BoostButtonComponent( + iconName: "Premium/Boosts/Giveaway", + title: "giveaway" + ) + ), + effectAlignment: .center, + action: { + component.createGiveaway() + } + ), + availableSize: CGSize(width: minButtonWidth, height: buttonHeight), + transition: context.transition + ) + context.add(giveawayButton + .position(CGPoint(x: context.availableSize.width / 2.0, y: size.height - 45.0)) + ) + + let featuresButton = featuresButton.update( + component: PlainButtonComponent( + content: AnyComponent( + BoostButtonComponent( + iconName: "Premium/Boosts/Features", + title: "features" + ) + ), + effectAlignment: .center, + action: { + component.openFeatures() + } + ), + availableSize: CGSize(width: minButtonWidth, height: buttonHeight), + transition: context.transition + ) + context.add(featuresButton + .position(CGPoint(x: context.availableSize.width - sideInset - minButtonWidth / 2.0, y: size.height - 45.0)) + ) + + return background.size + } + } +} + +private final class BoostButtonComponent: CombinedComponent { + let iconName: String + let title: String + + public init( + iconName: String, + title: String + ) { + self.iconName = iconName + self.title = title + } + + public static func ==(lhs: BoostButtonComponent, rhs: BoostButtonComponent) -> Bool { + if lhs.iconName != rhs.iconName { + return false + } + if lhs.title != rhs.title { + return false + } + return true + } + + public static var body: Body { + let background = Child(RoundedRectangle.self) + let icon = Child(BundleIconComponent.self) + let title = Child(MultilineTextComponent.self) + + return { context in + let size = context.availableSize + let component = context.component + + let background = background.update( + component: RoundedRectangle( + color: UIColor.white.withAlphaComponent(0.2), + cornerRadius: 10.0 + ), + availableSize: context.availableSize, + transition: context.transition + ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + let icon = icon.update( + component: BundleIconComponent( + name: component.iconName, + tintColor: .white + ), + availableSize: context.availableSize, + transition: context.transition + ) + context.add(icon + .position(CGPoint(x: size.width / 2.0, y: 21.0)) + ) + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.title, + font: Font.regular(11.0), + textColor: .white + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: size.width - 16.0, height: size.height), + transition: context.transition + ) + context.add(title + .position(CGPoint(x: size.width / 2.0, y: size.height - 16.0)) + ) + + return background.size + } + } +} diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 3c7500dff8..a6634ad10a 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -163,7 +163,7 @@ private enum StatsEntry: ItemListNodeEntry { case boostLevel(PresentationTheme, Int32, Int32, CGFloat) case boostOverviewTitle(PresentationTheme, String) - case boostOverview(PresentationTheme, ChannelBoostStatus) + case boostOverview(PresentationTheme, ChannelBoostStatus, Bool) case boostPrepaidTitle(PresentationTheme, String) case boostPrepaid(Int32, PresentationTheme, String, String, PrepaidGiveaway) @@ -505,8 +505,8 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } - case let .boostOverview(lhsTheme, lhsStats): - if case let .boostOverview(rhsTheme, rhsStats) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats { + case let .boostOverview(lhsTheme, lhsStats, lhsIsGroup): + if case let .boostOverview(rhsTheme, rhsStats, rhsIsGroup) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsIsGroup == rhsIsGroup { return true } else { return false @@ -631,7 +631,7 @@ private enum StatsEntry: ItemListNodeEntry { let .giftsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .overview(_, stats): - return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks) + return StatsOverviewItem(presentationData: presentationData, isGroup: false, stats: stats, sectionId: self.section, style: .blocks) case let .growthGraph(_, _, _, graph, type), let .followersGraph(_, _, _, graph, type), let .notificationsGraph(_, _, _, graph, type), @@ -730,8 +730,8 @@ private enum StatsEntry: ItemListNodeEntry { let inactiveText = presentationData.strings.ChannelBoost_Level("\(level)").string let activeText = presentationData.strings.ChannelBoost_Level("\(level + 1)").string return BoostLevelHeaderItem(theme: presentationData.theme, count: count, position: position, activeText: activeText, inactiveText: inactiveText, sectionId: self.section) - case let .boostOverview(_, stats): - return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks) + case let .boostOverview(_, stats, isGroup): + return StatsOverviewItem(presentationData: presentationData, isGroup: isGroup, stats: stats, sectionId: self.section, style: .blocks) case let .boostLink(_, link): let invite: ExportedInvitation = .link(link: link, title: nil, isPermanent: false, requestApproval: false, isRevoked: false, adminId: PeerId(0), date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil) return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { @@ -823,7 +823,7 @@ private struct ChannelStatsControllerState: Equatable { } -private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, stories: PeerStoryListContext.State?, interactions: [ChannelStatsPostInteractions.PostId: ChannelStatsPostInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, giftsState: ChannelBoostersContext.State?, presentationData: PresentationData, giveawayAvailable: Bool) -> [StatsEntry] { +private func channelStatsControllerEntries(state: ChannelStatsControllerState, peer: EnginePeer?, data: ChannelStats?, messages: [Message]?, stories: PeerStoryListContext.State?, interactions: [ChannelStatsPostInteractions.PostId: ChannelStatsPostInteractions]?, boostData: ChannelBoostStatus?, boostersState: ChannelBoostersContext.State?, giftsState: ChannelBoostersContext.State?, presentationData: PresentationData, giveawayAvailable: Bool, isGroup: Bool, boostsOnly: Bool) -> [StatsEntry] { var entries: [StatsEntry] = [] switch state.section { @@ -895,7 +895,6 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p entries.append(.storyReactionsByEmotionGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.storyReactionsByEmotionGraph, .bars)) } - if let peer, let interactions { var posts: [StatsPostItem] = [] if let messages { @@ -935,16 +934,18 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p } case .boosts: if let boostData { - let progress: CGFloat - if let nextLevelBoosts = boostData.nextLevelBoosts { - progress = CGFloat(boostData.boosts - boostData.currentLevelBoosts) / CGFloat(nextLevelBoosts - boostData.currentLevelBoosts) - } else { - progress = 1.0 + if !boostsOnly { + let progress: CGFloat + if let nextLevelBoosts = boostData.nextLevelBoosts { + progress = CGFloat(boostData.boosts - boostData.currentLevelBoosts) / CGFloat(nextLevelBoosts - boostData.currentLevelBoosts) + } else { + progress = 1.0 + } + entries.append(.boostLevel(presentationData.theme, Int32(boostData.boosts), Int32(boostData.level), progress)) } - entries.append(.boostLevel(presentationData.theme, Int32(boostData.boosts), Int32(boostData.level), progress)) entries.append(.boostOverviewTitle(presentationData.theme, presentationData.strings.Stats_Boosts_OverviewHeader)) - entries.append(.boostOverview(presentationData.theme, boostData)) + entries.append(.boostOverview(presentationData.theme, boostData, isGroup)) if !boostData.prepaidGiveaways.isEmpty { entries.append(.boostPrepaidTitle(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysTitle)) @@ -962,7 +963,7 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p if let boostersState, boostersState.count > 0 { boostersTitle = presentationData.strings.Stats_Boosts_Boosts(boostersState.count) boostersPlaceholder = nil - boostersFooter = presentationData.strings.Stats_Boosts_BoostersInfo + boostersFooter = isGroup ? "Your group is currently boosted by these members." : presentationData.strings.Stats_Boosts_BoostersInfo } else { boostersTitle = presentationData.strings.Stats_Boosts_BoostsNone boostersPlaceholder = presentationData.strings.Stats_Boosts_NoBoostersYet @@ -1033,11 +1034,11 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p entries.append(.boostLinkTitle(presentationData.theme, presentationData.strings.Stats_Boosts_LinkHeader)) entries.append(.boostLink(presentationData.theme, boostData.url)) - entries.append(.boostLinkInfo(presentationData.theme, presentationData.strings.Stats_Boosts_LinkInfo)) + entries.append(.boostLinkInfo(presentationData.theme, isGroup ? "Share this link with your members to get more boosts." : presentationData.strings.Stats_Boosts_LinkInfo)) if giveawayAvailable { entries.append(.gifts(presentationData.theme, presentationData.strings.Stats_Boosts_GetBoosts)) - entries.append(.giftsInfo(presentationData.theme, presentationData.strings.Stats_Boosts_GetBoostsInfo)) + entries.append(.giftsInfo(presentationData.theme, isGroup ? "Get more boosts for your group by gifting Premium to your subscribers." : presentationData.strings.Stats_Boosts_GetBoostsInfo)) } } } @@ -1098,8 +1099,10 @@ public func channelStatsController(context: AccountContext, updatedPresentationD var dismissAllTooltipsImpl: (() -> Void)? var presentImpl: ((ViewController) -> Void)? var pushImpl: ((ViewController) -> Void)? + var dismissImpl: (() -> Void)? var navigateToChatImpl: ((EnginePeer) -> Void)? var navigateToMessageImpl: ((EngineMessage.Id) -> Void)? + var openBoostImpl: ((Bool) -> Void)? let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal in return statsContext.loadDetailedGraph(graph, x: x) @@ -1256,6 +1259,11 @@ public func channelStatsController(context: AccountContext, updatedPresentationD ) |> deliverOnMainQueue |> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in + var isGroup = false + if let peer, case let .channel(channel) = peer, case .group = channel.info { + isGroup = true + } + let previous = previousData.swap(data) var emptyStateItem: ItemListControllerEmptyStateItem? switch state.section { @@ -1294,8 +1302,30 @@ public func channelStatsController(context: AccountContext, updatedPresentationD return map } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Stats_Statistics, presentationData.strings.Stats_Boosts], state.section == .boosts ? 1 : 0), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, presentationData: presentationData, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) + //TODO:localize + var title: ItemListControllerTitle + var headerItem: BoostHeaderItem? + var leftNavigationButton: ItemListNavigationButton? + var boostsOnly = false + if isGroup, section == .boosts, let boostStatus { + title = .text("") + headerItem = BoostHeaderItem(context: context, theme: presentationData.theme, strings: presentationData.strings, status: boostStatus, title: "Boost Group", text: "Members of your group can **boost** it so that it **levels up** and gets **exclusive features**.", openBoost: { + openBoostImpl?(false) + }, createGiveaway: { + arguments.openGifts() + }, openFeatures: { + openBoostImpl?(true) + }, back: { + dismissImpl?() + }) + leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}) + boostsOnly = true + } else { + title = .sectionControl([presentationData.strings.Stats_Statistics, presentationData.strings.Stats_Boosts], state.section == .boosts ? 1 : 0) + } + + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, presentationData: presentationData, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable, isGroup: isGroup, boostsOnly: boostsOnly), style: .blocks, emptyStateItem: emptyStateItem, headerItem: headerItem, crossfadeState: previous == nil, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -1436,6 +1466,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD pushImpl = { [weak controller] c in controller?.push(c) } + dismissImpl = { [weak controller] in + controller?.dismiss() + } navigateToChatImpl = { [weak controller] peer in if let navigationController = controller?.navigationController as? NavigationController { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil, forceOpenChat: true)) @@ -1454,6 +1487,36 @@ public func channelStatsController(context: AccountContext, updatedPresentationD } }) } + openBoostImpl = { [weak controller] features in + if features { + let boostController = PremiumBoostLevelsScreen( + context: context, + peerId: peerId, + mode: .features, + status: nil, + myBoostStatus: nil + ) + controller?.push(boostController) + } else { + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { [weak controller] boostStatus, myBoostStatus in + guard let boostStatus, let myBoostStatus else { + return + } + let boostController = PremiumBoostLevelsScreen( + context: context, + peerId: peerId, + mode: .user(mode: .current), + status: boostStatus, + myBoostStatus: myBoostStatus + ) + controller?.push(boostController) + }) + } + } return controller } diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index 452a4c8aa0..6c05f8096f 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -381,7 +381,7 @@ private enum StatsEntry: ItemListNodeEntry { let .topInvitersTitle(_, text, dates): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section) case let .overview(_, stats): - return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks) + return StatsOverviewItem(presentationData: presentationData, isGroup: true, stats: stats, sectionId: self.section, style: .blocks) case let .growthGraph(_, _, _, graph, type), let .membersGraph(_, _, _, graph, type), let .newMembersBySourceGraph(_, _, _, graph, type), diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index 4327d8489a..1c818fe3ed 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -160,7 +160,7 @@ private enum StatsEntry: ItemListNodeEntry { let .publicForwardsTitle(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .overview(_, stats, storyViews, publicShares): - return StatsOverviewItem(presentationData: presentationData, stats: stats as! Stats, storyViews: storyViews, publicShares: publicShares, sectionId: self.section, style: .blocks) + return StatsOverviewItem(presentationData: presentationData, isGroup: false, stats: stats as! Stats, storyViews: storyViews, publicShares: publicShares, sectionId: self.section, style: .blocks) case let .interactionsGraph(_, _, _, graph, type, noInitialZoom), let .reactionsGraph(_, _, _, graph, type, noInitialZoom): return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, noInitialZoom: noInitialZoom, getDetailsData: { date, completion in let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in diff --git a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift index a5f5e7d029..473e3a23cd 100644 --- a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift +++ b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift @@ -34,14 +34,16 @@ extension StoryStats: Stats { class StatsOverviewItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let isGroup: Bool let stats: Stats let storyViews: EngineStoryItem.Views? let publicShares: Int32? let sectionId: ItemListSectionId let style: ItemListStyle - init(presentationData: ItemListPresentationData, stats: Stats, storyViews: EngineStoryItem.Views? = nil, publicShares: Int32? = nil, sectionId: ItemListSectionId, style: ItemListStyle) { + init(presentationData: ItemListPresentationData, isGroup: Bool, stats: Stats, storyViews: EngineStoryItem.Views? = nil, publicShares: Int32? = nil, sectionId: ItemListSectionId, style: ItemListStyle) { self.presentationData = presentationData + self.isGroup = isGroup self.stats = stats self.storyViews = storyViews self.publicShares = publicShares @@ -403,7 +405,7 @@ class StatsOverviewItemNode: ListViewItemNode { params.width, item.presentationData, "≈\(Int(stats.premiumAudience?.value ?? 0))", - item.presentationData.strings.Stats_Boosts_PremiumSubscribers, + item.isGroup ? item.presentationData.strings.Stats_Boosts_PremiumMembers : item.presentationData.strings.Stats_Boosts_PremiumSubscribers, (String(format: "%.02f%%", premiumSubscribers * 100.0), .generic) ) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 0c966f3ef9..53e973f912 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -741,7 +741,11 @@ private final class StickerPackContainer: ASDisplayNode { if let currentContents = self.currentContents?.first { let buttonColor: UIColor var buttonFont: UIFont = Font.semibold(17.0) - switch currentContents { + + if let controller = self.controller, let _ = controller.mainActionTitle { + buttonColor = self.presentationData.theme.list.itemCheckColors.foregroundColor + } else { + switch currentContents { case .fetching: buttonColor = .clear case .none: @@ -751,7 +755,9 @@ private final class StickerPackContainer: ASDisplayNode { if installed { buttonFont = Font.regular(17.0) } + } } + self.buttonNode.setTitle(self.buttonNode.attributedTitle(for: .normal)?.string ?? "", with: buttonFont, with: buttonColor, for: .normal) } @@ -1134,33 +1140,43 @@ private final class StickerPackContainer: ASDisplayNode { } } - if installed { - let text: String - if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - text = self.presentationData.strings.StickerPack_RemoveStickerCount(Int32(entries.count)) - } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - text = self.presentationData.strings.StickerPack_RemoveEmojiCount(Int32(items.count)) - } else { - text = self.presentationData.strings.StickerPack_RemoveMaskCount(Int32(entries.count)) - } - self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal) - self.buttonNode.setBackgroundImage(nil, for: []) - } else { - let text: String - if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - text = self.presentationData.strings.StickerPack_AddStickerCount(Int32(entries.count)) - } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - text = self.presentationData.strings.StickerPack_AddEmojiCount(Int32(items.count)) - } else { - text = self.presentationData.strings.StickerPack_AddMaskCount(Int32(entries.count)) - } - self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) + if let mainActionTitle = self.controller?.mainActionTitle { + self.buttonNode.setTitle(mainActionTitle, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) })?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11) self.buttonNode.setBackgroundImage(roundedAccentBackground, for: []) + } else { + if installed { + let text: String + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + text = self.presentationData.strings.StickerPack_RemoveStickerCount(Int32(entries.count)) + } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { + text = self.presentationData.strings.StickerPack_RemoveEmojiCount(Int32(items.count)) + } else { + text = self.presentationData.strings.StickerPack_RemoveMaskCount(Int32(entries.count)) + } + self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal) + self.buttonNode.setBackgroundImage(nil, for: []) + } else { + let text: String + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + text = self.presentationData.strings.StickerPack_AddStickerCount(Int32(entries.count)) + } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { + text = self.presentationData.strings.StickerPack_AddEmojiCount(Int32(items.count)) + } else { + text = self.presentationData.strings.StickerPack_AddMaskCount(Int32(entries.count)) + } + self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) + let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + })?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11) + self.buttonNode.setBackgroundImage(roundedAccentBackground, for: []) + } } } } @@ -1225,24 +1241,28 @@ private final class StickerPackContainer: ASDisplayNode { var buttonHeight: CGFloat = 50.0 var actionAreaTopInset: CGFloat = 8.0 var actionAreaBottomInset: CGFloat = 16.0 - if !self.currentStickerPacks.isEmpty { - var installedCount = 0 - for (_, _, isInstalled) in self.currentStickerPacks { - if isInstalled { - installedCount += 1 + if let _ = self.controller?.mainActionTitle { + + } else { + if !self.currentStickerPacks.isEmpty { + var installedCount = 0 + for (_, _, isInstalled) in self.currentStickerPacks { + if isInstalled { + installedCount += 1 + } + } + if installedCount == self.currentStickerPacks.count { + buttonHeight = 42.0 + actionAreaTopInset = 1.0 + actionAreaBottomInset = 2.0 } } - if installedCount == self.currentStickerPacks.count { + if let (_, _, isInstalled) = self.currentStickerPack, isInstalled { buttonHeight = 42.0 actionAreaTopInset = 1.0 actionAreaBottomInset = 2.0 } } - if let (_, _, isInstalled) = self.currentStickerPack, isInstalled { - buttonHeight = 42.0 - actionAreaTopInset = 1.0 - actionAreaBottomInset = 2.0 - } let buttonSideInset: CGFloat = 16.0 let titleAreaInset: CGFloat = 56.0 @@ -1910,6 +1930,7 @@ public final class StickerPackScreenImpl: ViewController { let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer + let mainActionTitle: String? let actionTitle: String? public init( @@ -1918,6 +1939,7 @@ public final class StickerPackScreenImpl: ViewController { stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], selectedStickerPackIndex: Int = 0, + mainActionTitle: String? = nil, actionTitle: String? = nil, parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, @@ -1930,6 +1952,7 @@ public final class StickerPackScreenImpl: ViewController { self.stickerPacks = stickerPacks self.loadedStickerPacks = loadedStickerPacks self.initialSelectedStickerPackIndex = selectedStickerPackIndex + self.mainActionTitle = mainActionTitle self.actionTitle = actionTitle self.parentNavigationController = parentNavigationController self.sendSticker = sendSticker @@ -2161,6 +2184,7 @@ public func StickerPackScreen( mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack] = [], + mainActionTitle: String? = nil, actionTitle: String? = nil, parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, @@ -2175,6 +2199,7 @@ public func StickerPackScreen( stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0, + mainActionTitle: mainActionTitle, actionTitle: actionTitle, parentNavigationController: parentNavigationController, sendSticker: sendSticker, diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 54f3b0acb3..be8ecd8eb8 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -105,6 +105,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1427671598] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeAbout($0) } dict[-1102180616] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeAvailableReactions($0) } dict[1051328177] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeEmojiStatus($0) } + dict[1188577451] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeEmojiStickerSet($0) } dict[1855199800] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeHistoryTTL($0) } dict[84703944] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLinkedChat($0) } dict[241923758] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLocation($0) } @@ -176,7 +177,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1605510357] = { return Api.ChatAdminRights.parse_chatAdminRights($0) } dict[-219353309] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) } dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) } - dict[254528367] = { return Api.ChatFull.parse_channelFull($0) } + dict[1153455271] = { return Api.ChatFull.parse_channelFull($0) } dict[-908914376] = { return Api.ChatFull.parse_chatFull($0) } dict[-840897472] = { return Api.ChatInvite.parse_chatInvite($0) } dict[1516793212] = { return Api.ChatInvite.parse_chatInviteAlready($0) } @@ -472,9 +473,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) } dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[64088654] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[1992213009] = { return Api.Message.parse_message($0) } + dict[508332649] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[721967202] = { return Api.Message.parse_messageService($0) } + dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } dict[-988359047] = { return Api.MessageAction.parse_messageActionBotAllowed($0) } dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) } dict[-365344535] = { return Api.MessageAction.parse_messageActionChannelMigrateFrom($0) } @@ -1104,7 +1106,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) } dict[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($0) } dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) } - dict[-276549461] = { return Api.help.PeerColorOption.parse_peerColorOption($0) } + dict[-1377014082] = { return Api.help.PeerColorOption.parse_peerColorOption($0) } dict[1987928555] = { return Api.help.PeerColorSet.parse_peerColorProfileSet($0) } dict[639736408] = { return Api.help.PeerColorSet.parse_peerColorSet($0) } dict[16313608] = { return Api.help.PeerColors.parse_peerColors($0) } @@ -1274,7 +1276,7 @@ public extension Api { return parser(reader) } else { - telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found") + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") return nil } } diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index c1740387cb..f63228f098 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -424,19 +424,20 @@ public extension Api { } public extension Api { indirect enum Message: TypeConstructorDescription { - case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: 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?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?) + case message(flags: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: 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?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) 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) { switch self { - case .message(let flags, let id, let fromId, let peerId, let savedPeerId, 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 reactions, let restrictionReason, let ttlPeriod): + case .message(let flags, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, 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 reactions, let restrictionReason, let ttlPeriod): if boxed { - buffer.appendInt32(1992213009) + buffer.appendInt32(508332649) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 29) != 0 {serializeInt32(fromBoostsApplied!, buffer: buffer, boxed: false)} peerId.serialize(buffer, true) if Int(flags) & Int(1 << 28) != 0 {savedPeerId!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} @@ -491,8 +492,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .message(let flags, let id, let fromId, let peerId, let savedPeerId, 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 reactions, let restrictionReason, let ttlPeriod): - return ("message", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any)]) + case .message(let flags, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, 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 reactions, let restrictionReason, let ttlPeriod): + return ("message", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod): @@ -509,88 +510,91 @@ public extension Api { if Int(_1!) & Int(1 << 8) != 0 {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 _4: Int32? + if Int(_1!) & Int(1 << 29) != 0 {_4 = reader.readInt32() } var _5: Api.Peer? - if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() { + if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _6: Api.Peer? + if Int(_1!) & Int(1 << 28) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Peer } } - var _6: Api.MessageFwdHeader? + var _7: Api.MessageFwdHeader? if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + _7 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader } } - var _7: Int64? - if Int(_1!) & Int(1 << 11) != 0 {_7 = reader.readInt64() } - var _8: Api.MessageReplyHeader? + var _8: Int64? + if Int(_1!) & Int(1 << 11) != 0 {_8 = reader.readInt64() } + var _9: Api.MessageReplyHeader? if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + _9 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader } } - var _9: Int32? - _9 = reader.readInt32() - var _10: String? - _10 = parseString(reader) - var _11: Api.MessageMedia? + var _10: Int32? + _10 = reader.readInt32() + var _11: String? + _11 = parseString(reader) + var _12: Api.MessageMedia? if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.MessageMedia + _12 = Api.parse(reader, signature: signature) as? Api.MessageMedia } } - var _12: Api.ReplyMarkup? + var _13: Api.ReplyMarkup? if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + _13 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } - var _13: [Api.MessageEntity]? + var _14: [Api.MessageEntity]? if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _14: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_14 = reader.readInt32() } var _15: Int32? if Int(_1!) & Int(1 << 10) != 0 {_15 = reader.readInt32() } - var _16: Api.MessageReplies? + var _16: Int32? + if Int(_1!) & Int(1 << 10) != 0 {_16 = reader.readInt32() } + var _17: Api.MessageReplies? if Int(_1!) & Int(1 << 23) != 0 {if let signature = reader.readInt32() { - _16 = Api.parse(reader, signature: signature) as? Api.MessageReplies + _17 = Api.parse(reader, signature: signature) as? Api.MessageReplies } } - var _17: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() } - var _18: String? - if Int(_1!) & Int(1 << 16) != 0 {_18 = parseString(reader) } - var _19: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_19 = reader.readInt64() } - var _20: Api.MessageReactions? + var _18: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() } + var _19: String? + if Int(_1!) & Int(1 << 16) != 0 {_19 = parseString(reader) } + var _20: Int64? + if Int(_1!) & Int(1 << 17) != 0 {_20 = reader.readInt64() } + var _21: Api.MessageReactions? if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.MessageReactions + _21 = Api.parse(reader, signature: signature) as? Api.MessageReactions } } - var _21: [Api.RestrictionReason]? + var _22: [Api.RestrictionReason]? if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { - _21 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + _22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } - var _22: Int32? - if Int(_1!) & Int(1 << 25) != 0 {_22 = reader.readInt32() } + var _23: Int32? + if Int(_1!) & Int(1 << 25) != 0 {_23 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 28) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = _9 != nil + let _c4 = (Int(_1!) & Int(1 << 29) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 28) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 11) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil let _c10 = _10 != nil - let _c11 = (Int(_1!) & Int(1 << 9) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 7) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 10) == 0) || _14 != nil + let _c11 = _11 != nil + let _c12 = (Int(_1!) & Int(1 << 9) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 6) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 7) == 0) || _14 != nil let _c15 = (Int(_1!) & Int(1 << 10) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 23) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 16) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 17) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 20) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 22) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 25) == 0) || _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, peerId: _4!, savedPeerId: _5, fwdFrom: _6, viaBotId: _7, replyTo: _8, date: _9!, message: _10!, media: _11, replyMarkup: _12, entities: _13, views: _14, forwards: _15, replies: _16, editDate: _17, postAuthor: _18, groupedId: _19, reactions: _20, restrictionReason: _21, ttlPeriod: _22) + let _c16 = (Int(_1!) & Int(1 << 10) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 23) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 16) == 0) || _19 != nil + let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil + let _c21 = (Int(_1!) & Int(1 << 20) == 0) || _21 != nil + let _c22 = (Int(_1!) & Int(1 << 22) == 0) || _22 != nil + let _c23 = (Int(_1!) & Int(1 << 25) == 0) || _23 != 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 { + return Api.Message.message(flags: _1!, id: _2!, fromId: _3, fromBoostsApplied: _4, peerId: _5!, savedPeerId: _6, fwdFrom: _7, viaBotId: _8, replyTo: _9, date: _10!, message: _11!, media: _12, replyMarkup: _13, entities: _14, views: _15, forwards: _16, replies: _17, editDate: _18, postAuthor: _19, groupedId: _20, reactions: _21, restrictionReason: _22, ttlPeriod: _23) } else { return nil @@ -660,6 +664,7 @@ public extension Api { } public extension Api { enum MessageAction: TypeConstructorDescription { + case messageActionBoostApply(boosts: Int32) case messageActionBotAllowed(flags: Int32, domain: String?, app: Api.BotApp?) case messageActionChannelCreate(title: String) case messageActionChannelMigrateFrom(title: String, chatId: Int64) @@ -704,6 +709,12 @@ public extension Api { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { + case .messageActionBoostApply(let boosts): + if boxed { + buffer.appendInt32(-872240531) + } + serializeInt32(boosts, buffer: buffer, boxed: false) + break case .messageActionBotAllowed(let flags, let domain, let app): if boxed { buffer.appendInt32(-988359047) @@ -1028,6 +1039,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { + case .messageActionBoostApply(let boosts): + return ("messageActionBoostApply", [("boosts", boosts as Any)]) case .messageActionBotAllowed(let flags, let domain, let app): return ("messageActionBotAllowed", [("flags", flags as Any), ("domain", domain as Any), ("app", app as Any)]) case .messageActionChannelCreate(let title): @@ -1113,6 +1126,17 @@ public extension Api { } } + public static func parse_messageActionBoostApply(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionBoostApply(boosts: _1!) + } + else { + return nil + } + } public static func parse_messageActionBotAllowed(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index e8bb5e4205..5f696bd03a 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -725,6 +725,7 @@ public extension Api { case channelAdminLogEventActionChangeAbout(prevValue: String, newValue: String) case channelAdminLogEventActionChangeAvailableReactions(prevValue: Api.ChatReactions, newValue: Api.ChatReactions) case channelAdminLogEventActionChangeEmojiStatus(prevValue: Api.EmojiStatus, newValue: Api.EmojiStatus) + case channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset: Api.InputStickerSet, newStickerset: Api.InputStickerSet) case channelAdminLogEventActionChangeHistoryTTL(prevValue: Int32, newValue: Int32) case channelAdminLogEventActionChangeLinkedChat(prevValue: Int64, newValue: Int64) case channelAdminLogEventActionChangeLocation(prevValue: Api.ChannelLocation, newValue: Api.ChannelLocation) @@ -793,6 +794,13 @@ public extension Api { prevValue.serialize(buffer, true) newValue.serialize(buffer, true) break + case .channelAdminLogEventActionChangeEmojiStickerSet(let prevStickerset, let newStickerset): + if boxed { + buffer.appendInt32(1188577451) + } + prevStickerset.serialize(buffer, true) + newStickerset.serialize(buffer, true) + break case .channelAdminLogEventActionChangeHistoryTTL(let prevValue, let newValue): if boxed { buffer.appendInt32(1855199800) @@ -1098,6 +1106,8 @@ public extension Api { return ("channelAdminLogEventActionChangeAvailableReactions", [("prevValue", prevValue as Any), ("newValue", newValue as Any)]) case .channelAdminLogEventActionChangeEmojiStatus(let prevValue, let newValue): return ("channelAdminLogEventActionChangeEmojiStatus", [("prevValue", prevValue as Any), ("newValue", newValue as Any)]) + case .channelAdminLogEventActionChangeEmojiStickerSet(let prevStickerset, let newStickerset): + return ("channelAdminLogEventActionChangeEmojiStickerSet", [("prevStickerset", prevStickerset as Any), ("newStickerset", newStickerset as Any)]) case .channelAdminLogEventActionChangeHistoryTTL(let prevValue, let newValue): return ("channelAdminLogEventActionChangeHistoryTTL", [("prevValue", prevValue as Any), ("newValue", newValue as Any)]) case .channelAdminLogEventActionChangeLinkedChat(let prevValue, let newValue): @@ -1239,6 +1249,24 @@ public extension Api { return nil } } + public static func parse_channelAdminLogEventActionChangeEmojiStickerSet(_ reader: BufferReader) -> ChannelAdminLogEventAction? { + var _1: Api.InputStickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet + } + var _2: Api.InputStickerSet? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset: _1!, newStickerset: _2!) + } + else { + return nil + } + } public static func parse_channelAdminLogEventActionChangeHistoryTTL(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index 60f1674a87..e688ea037b 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -1104,27 +1104,28 @@ public extension Api.help { } public extension Api.help { enum PeerColorOption: TypeConstructorDescription { - case peerColorOption(flags: Int32, colorId: Int32, colors: Api.help.PeerColorSet?, darkColors: Api.help.PeerColorSet?, channelMinLevel: Int32?) + case peerColorOption(flags: Int32, colorId: Int32, colors: Api.help.PeerColorSet?, darkColors: Api.help.PeerColorSet?, channelMinLevel: Int32?, groupMinLevel: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel): + case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel, let groupMinLevel): if boxed { - buffer.appendInt32(-276549461) + buffer.appendInt32(-1377014082) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(colorId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {colors!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {darkColors!.serialize(buffer, true)} if Int(flags) & Int(1 << 3) != 0 {serializeInt32(channelMinLevel!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(groupMinLevel!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel): - return ("peerColorOption", [("flags", flags as Any), ("colorId", colorId as Any), ("colors", colors as Any), ("darkColors", darkColors as Any), ("channelMinLevel", channelMinLevel as Any)]) + case .peerColorOption(let flags, let colorId, let colors, let darkColors, let channelMinLevel, let groupMinLevel): + return ("peerColorOption", [("flags", flags as Any), ("colorId", colorId as Any), ("colors", colors as Any), ("darkColors", darkColors as Any), ("channelMinLevel", channelMinLevel as Any), ("groupMinLevel", groupMinLevel as Any)]) } } @@ -1143,13 +1144,16 @@ public extension Api.help { } } var _5: Int32? if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.help.PeerColorOption.peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5) + let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.help.PeerColorOption.peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5, groupMinLevel: _6) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 3baff622ff..1548b8695b 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -920,14 +920,14 @@ public extension Api { } public extension Api { enum ChatFull: TypeConstructorDescription { - case channelFull(flags: Int32, flags2: 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?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, defaultSendAs: Api.Peer?, availableReactions: Api.ChatReactions?, stories: Api.PeerStories?, wallpaper: Api.WallPaper?) + case channelFull(flags: Int32, flags2: 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?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, defaultSendAs: Api.Peer?, availableReactions: Api.ChatReactions?, stories: Api.PeerStories?, wallpaper: Api.WallPaper?, boostsApplied: Int32?, boostsUnrestrict: Int32?, emojiset: Api.StickerSet?) 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?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, availableReactions: Api.ChatReactions?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .channelFull(let flags, let flags2, 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, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions, let stories, let wallpaper): + case .channelFull(let flags, let flags2, 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, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions, let stories, let wallpaper, let boostsApplied, let boostsUnrestrict, let emojiset): if boxed { - buffer.appendInt32(254528367) + buffer.appendInt32(1153455271) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -980,6 +980,9 @@ public extension Api { if Int(flags) & Int(1 << 30) != 0 {availableReactions!.serialize(buffer, true)} if Int(flags2) & Int(1 << 4) != 0 {stories!.serialize(buffer, true)} if Int(flags2) & Int(1 << 7) != 0 {wallpaper!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 8) != 0 {serializeInt32(boostsApplied!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 9) != 0 {serializeInt32(boostsUnrestrict!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 10) != 0 {emojiset!.serialize(buffer, true)} break 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, let requestsPending, let recentRequesters, let availableReactions): if boxed { @@ -1016,8 +1019,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .channelFull(let flags, let flags2, 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, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions, let stories, let wallpaper): - return ("channelFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("participantsCount", participantsCount as Any), ("adminsCount", adminsCount as Any), ("kickedCount", kickedCount as Any), ("bannedCount", bannedCount as Any), ("onlineCount", onlineCount as Any), ("readInboxMaxId", readInboxMaxId as Any), ("readOutboxMaxId", readOutboxMaxId as Any), ("unreadCount", unreadCount as Any), ("chatPhoto", chatPhoto as Any), ("notifySettings", notifySettings as Any), ("exportedInvite", exportedInvite as Any), ("botInfo", botInfo as Any), ("migratedFromChatId", migratedFromChatId as Any), ("migratedFromMaxId", migratedFromMaxId as Any), ("pinnedMsgId", pinnedMsgId as Any), ("stickerset", stickerset as Any), ("availableMinId", availableMinId as Any), ("folderId", folderId as Any), ("linkedChatId", linkedChatId as Any), ("location", location as Any), ("slowmodeSeconds", slowmodeSeconds as Any), ("slowmodeNextSendDate", slowmodeNextSendDate as Any), ("statsDc", statsDc as Any), ("pts", pts as Any), ("call", call as Any), ("ttlPeriod", ttlPeriod as Any), ("pendingSuggestions", pendingSuggestions as Any), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs as Any), ("themeEmoticon", themeEmoticon as Any), ("requestsPending", requestsPending as Any), ("recentRequesters", recentRequesters as Any), ("defaultSendAs", defaultSendAs as Any), ("availableReactions", availableReactions as Any), ("stories", stories as Any), ("wallpaper", wallpaper as Any)]) + case .channelFull(let flags, let flags2, 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, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions, let stories, let wallpaper, let boostsApplied, let boostsUnrestrict, let emojiset): + return ("channelFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("participantsCount", participantsCount as Any), ("adminsCount", adminsCount as Any), ("kickedCount", kickedCount as Any), ("bannedCount", bannedCount as Any), ("onlineCount", onlineCount as Any), ("readInboxMaxId", readInboxMaxId as Any), ("readOutboxMaxId", readOutboxMaxId as Any), ("unreadCount", unreadCount as Any), ("chatPhoto", chatPhoto as Any), ("notifySettings", notifySettings as Any), ("exportedInvite", exportedInvite as Any), ("botInfo", botInfo as Any), ("migratedFromChatId", migratedFromChatId as Any), ("migratedFromMaxId", migratedFromMaxId as Any), ("pinnedMsgId", pinnedMsgId as Any), ("stickerset", stickerset as Any), ("availableMinId", availableMinId as Any), ("folderId", folderId as Any), ("linkedChatId", linkedChatId as Any), ("location", location as Any), ("slowmodeSeconds", slowmodeSeconds as Any), ("slowmodeNextSendDate", slowmodeNextSendDate as Any), ("statsDc", statsDc as Any), ("pts", pts as Any), ("call", call as Any), ("ttlPeriod", ttlPeriod as Any), ("pendingSuggestions", pendingSuggestions as Any), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs as Any), ("themeEmoticon", themeEmoticon as Any), ("requestsPending", requestsPending as Any), ("recentRequesters", recentRequesters as Any), ("defaultSendAs", defaultSendAs as Any), ("availableReactions", availableReactions as Any), ("stories", stories as Any), ("wallpaper", wallpaper as Any), ("boostsApplied", boostsApplied as Any), ("boostsUnrestrict", boostsUnrestrict as Any), ("emojiset", emojiset as Any)]) 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, let requestsPending, let recentRequesters, let availableReactions): return ("chatFull", [("flags", flags as Any), ("id", id as Any), ("about", about as Any), ("participants", participants as Any), ("chatPhoto", chatPhoto as Any), ("notifySettings", notifySettings as Any), ("exportedInvite", exportedInvite as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("folderId", folderId as Any), ("call", call as Any), ("ttlPeriod", ttlPeriod as Any), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs as Any), ("themeEmoticon", themeEmoticon as Any), ("requestsPending", requestsPending as Any), ("recentRequesters", recentRequesters as Any), ("availableReactions", availableReactions as Any)]) } @@ -1130,6 +1133,14 @@ public extension Api { if Int(_2!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { _39 = Api.parse(reader, signature: signature) as? Api.WallPaper } } + var _40: Int32? + if Int(_2!) & Int(1 << 8) != 0 {_40 = reader.readInt32() } + var _41: Int32? + if Int(_2!) & Int(1 << 9) != 0 {_41 = reader.readInt32() } + var _42: Api.StickerSet? + if Int(_2!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { + _42 = Api.parse(reader, signature: signature) as? Api.StickerSet + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1169,8 +1180,11 @@ public extension Api { let _c37 = (Int(_1!) & Int(1 << 30) == 0) || _37 != nil let _c38 = (Int(_2!) & Int(1 << 4) == 0) || _38 != nil let _c39 = (Int(_2!) & Int(1 << 7) == 0) || _39 != 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 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 { - return Api.ChatFull.channelFull(flags: _1!, flags2: _2!, id: _3!, about: _4!, participantsCount: _5, adminsCount: _6, kickedCount: _7, bannedCount: _8, onlineCount: _9, readInboxMaxId: _10!, readOutboxMaxId: _11!, unreadCount: _12!, chatPhoto: _13!, notifySettings: _14!, exportedInvite: _15, botInfo: _16!, migratedFromChatId: _17, migratedFromMaxId: _18, pinnedMsgId: _19, stickerset: _20, availableMinId: _21, folderId: _22, linkedChatId: _23, location: _24, slowmodeSeconds: _25, slowmodeNextSendDate: _26, statsDc: _27, pts: _28!, call: _29, ttlPeriod: _30, pendingSuggestions: _31, groupcallDefaultJoinAs: _32, themeEmoticon: _33, requestsPending: _34, recentRequesters: _35, defaultSendAs: _36, availableReactions: _37, stories: _38, wallpaper: _39) + let _c40 = (Int(_2!) & Int(1 << 8) == 0) || _40 != nil + let _c41 = (Int(_2!) & Int(1 << 9) == 0) || _41 != nil + let _c42 = (Int(_2!) & Int(1 << 10) == 0) || _42 != 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 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 && _c41 && _c42 { + return Api.ChatFull.channelFull(flags: _1!, flags2: _2!, id: _3!, about: _4!, participantsCount: _5, adminsCount: _6, kickedCount: _7, bannedCount: _8, onlineCount: _9, readInboxMaxId: _10!, readOutboxMaxId: _11!, unreadCount: _12!, chatPhoto: _13!, notifySettings: _14!, exportedInvite: _15, botInfo: _16!, migratedFromChatId: _17, migratedFromMaxId: _18, pinnedMsgId: _19, stickerset: _20, availableMinId: _21, folderId: _22, linkedChatId: _23, location: _24, slowmodeSeconds: _25, slowmodeNextSendDate: _26, statsDc: _27, pts: _28!, call: _29, ttlPeriod: _30, pendingSuggestions: _31, groupcallDefaultJoinAs: _32, themeEmoticon: _33, requestsPending: _34, recentRequesters: _35, defaultSendAs: _36, availableReactions: _37, stories: _38, wallpaper: _39, boostsApplied: _40, boostsUnrestrict: _41, emojiset: _42) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index 059e3a9ee7..d280ba8d81 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -2887,6 +2887,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func setBoostsToUnblockRestrictions(channel: Api.InputChannel, boosts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1388733202) + channel.serialize(buffer, true) + serializeInt32(boosts, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.setBoostsToUnblockRestrictions", parameters: [("channel", String(describing: channel)), ("boosts", String(describing: boosts))]), 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 extension Api.functions.channels { static func setDiscussionGroup(broadcast: Api.InputChannel, group: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2903,6 +2919,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func setEmojiStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1020866743) + channel.serialize(buffer, true) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "channels.setEmojiStickers", parameters: [("channel", String(describing: channel)), ("stickerset", String(describing: stickerset))]), 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 extension Api.functions.channels { static func setStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index c9e7ec6df1..9ebfae3d48 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -960,6 +960,17 @@ public final class ManagedAudioSession: NSObject { try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) } + if case let .record(_, video, _) = type, video, let input = AVAudioSession.sharedInstance().availableInputs?.first { + if let dataSources = input.dataSources { + for source in dataSources { + if source.dataSourceName.contains("Bottom") { + try? input.setPreferredDataSource(source) + break + } + } + } + } + if resetToBuiltin { var updatedType = type if case .record(false, let video, let withOthers) = updatedType, self.isHeadsetPluggedInValue { diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 12531720b9..84789814ab 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -101,6 +101,7 @@ private var declaredEncodables: Void = { declareEncodable(TelegramMediaWebpage.self, f: { TelegramMediaWebpage(decoder: $0) }) declareEncodable(ViewCountMessageAttribute.self, f: { ViewCountMessageAttribute(decoder: $0) }) declareEncodable(ForwardCountMessageAttribute.self, f: { ForwardCountMessageAttribute(decoder: $0) }) + declareEncodable(BoostCountMessageAttribute.self, f: { BoostCountMessageAttribute(decoder: $0) }) declareEncodable(NotificationInfoMessageAttribute.self, f: { NotificationInfoMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaAction.self, f: { TelegramMediaAction(decoder: $0) }) declareEncodable(TelegramPeerNotificationSettings.self, f: { TelegramPeerNotificationSettings(decoder: $0) }) diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index c77129451d..ec3557d83e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -126,7 +126,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -142,7 +142,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(_, _, fromId, chatPeerId, savedPeerId, fwdHeader, viaBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _): + case let .message(_, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _): let peerId: PeerId = chatPeerId.peerId var result = [peerId] @@ -224,7 +224,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, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) @@ -263,7 +263,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, id, _, chatPeerId, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, id, _, _, chatPeerId, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -597,7 +597,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, chatPeerId, savedPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod): + case let .message(flags, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod): let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId let peerId: PeerId @@ -800,6 +800,12 @@ extension StoreMessage { attributes.append(ForwardCountMessageAttribute(count: Int(forwards))) } } + + if namespace == Namespaces.Message.Cloud { + if let boosts = boosts { + attributes.append(BoostCountMessageAttribute(count: Int(boosts))) + } + } if let editDate = editDate { attributes.append(EditedMessageAttribute(date: editDate, isHidden: (flags & (1 << 21)) != 0)) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index dda3010fa8..76454c3353 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -135,6 +135,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .giveawayLaunched) case let .messageActionGiveawayResults(winners, unclaimed): return TelegramMediaAction(action: .giveawayResults(winners: winners, unclaimed: unclaimed)) + case let .messageActionBoostApply(boosts): + return TelegramMediaAction(action: .boostsApplied(boosts: boosts)) } } diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 1917501ec3..52ba6b348c 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -1138,7 +1138,8 @@ public func _internal_searchForumTopics(account: Account, peerId: EnginePeer.Id, autoremoveTimeout: nil, storyStats: nil, displayAsTopicList: false, - isPremiumRequiredToMessage: false + isPremiumRequiredToMessage: false, + mediaDraftContentType: nil )) } diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index cd37ccc932..6bb8c76657 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -96,7 +96,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break diff --git a/submodules/TelegramCore/Sources/State/ChannelBoost.swift b/submodules/TelegramCore/Sources/State/ChannelBoost.swift index e6853b7f0f..2e84bd5978 100644 --- a/submodules/TelegramCore/Sources/State/ChannelBoost.swift +++ b/submodules/TelegramCore/Sources/State/ChannelBoost.swift @@ -76,6 +76,10 @@ public struct ChannelBoostStatus: Equatable { } return true } + + public func withUpdated(boosts: Int) -> ChannelBoostStatus { + return ChannelBoostStatus(level: self.level, boosts: boosts, giftBoosts: self.giftBoosts, currentLevelBoosts: self.currentLevelBoosts, nextLevelBoosts: self.nextLevelBoosts, premiumAudience: self.premiumAudience, url: self.url, prepaidGiveaways: self.prepaidGiveaways, boostedByMe: self.boostedByMe) + } } func _internal_getChannelBoostStatus(account: Account, peerId: PeerId) -> Signal { @@ -125,7 +129,26 @@ func _internal_applyChannelBoost(account: Account, peerId: PeerId, slots: [Int32 |> mapToSignal { result -> Signal in if let result = result { return account.postbox.transaction { transaction -> MyBoostStatus? in - return MyBoostStatus(apiMyBoostStatus: result, accountPeerId: account.peerId, transaction: transaction) + let myBoostStatus = MyBoostStatus(apiMyBoostStatus: result, accountPeerId: account.peerId, transaction: transaction) + + var appliedBoosts: Int32 = 0 + for boost in myBoostStatus.boosts { + if boost.peer?.id == peerId { + appliedBoosts += 1 + } + } + + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + var cachedData: CachedChannelData + if let current = current as? CachedChannelData { + cachedData = current + } else { + cachedData = CachedChannelData() + } + return cachedData.withUpdatedAppliedBoosts(appliedBoosts) + }) + + return myBoostStatus } } else { return .single(nil) diff --git a/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift index 76c5dc6fcc..a75ad876ba 100644 --- a/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift +++ b/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift @@ -95,18 +95,21 @@ public final class EngineAvailableColorOptions: Codable, Equatable { case dark = "d" case isHidden = "h" case requiredChannelMinBoostLevel = "rcmb" + case requiredGroupMinBoostLevel = "rgmb" } public let light: ColorOption public let dark: ColorOption? public let isHidden: Bool public let requiredChannelMinBoostLevel: Int32? + public let requiredGroupMinBoostLevel: Int32? - public init(light: ColorOption, dark: ColorOption?, isHidden: Bool, requiredChannelMinBoostLevel: Int32?) { + public init(light: ColorOption, dark: ColorOption?, isHidden: Bool, requiredChannelMinBoostLevel: Int32?, requiredGroupMinBoostLevel: Int32?) { self.light = light self.dark = dark self.isHidden = isHidden self.requiredChannelMinBoostLevel = requiredChannelMinBoostLevel + self.requiredGroupMinBoostLevel = requiredGroupMinBoostLevel } public init(from decoder: Decoder) throws { @@ -116,6 +119,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable { self.dark = try container.decodeIfPresent(ColorOption.self, forKey: .dark) self.isHidden = try container.decode(Bool.self, forKey: .isHidden) self.requiredChannelMinBoostLevel = try container.decodeIfPresent(Int32.self, forKey: .requiredChannelMinBoostLevel) + self.requiredGroupMinBoostLevel = try container.decodeIfPresent(Int32.self, forKey: .requiredGroupMinBoostLevel) } public func encode(to encoder: Encoder) throws { @@ -125,6 +129,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable { try container.encodeIfPresent(self.dark, forKey: .dark) try container.encodeIfPresent(self.isHidden, forKey: .isHidden) try container.encodeIfPresent(self.requiredChannelMinBoostLevel, forKey: .requiredChannelMinBoostLevel) + try container.encodeIfPresent(self.requiredGroupMinBoostLevel, forKey: .requiredGroupMinBoostLevel) } public static func ==(lhs: ColorOptionPack, rhs: ColorOptionPack) -> Bool { @@ -143,6 +148,9 @@ public final class EngineAvailableColorOptions: Codable, Equatable { if lhs.requiredChannelMinBoostLevel != rhs.requiredChannelMinBoostLevel { return false } + if lhs.requiredGroupMinBoostLevel != rhs.requiredGroupMinBoostLevel { + return false + } return true } } @@ -270,14 +278,14 @@ private extension EngineAvailableColorOptions { var mappedOptions: [Option] = [] for apiColor in apiColors { switch apiColor { - case let .peerColorOption(flags, colorId, colors, darkColors, requiredChannelMinBoostLevel): + case let .peerColorOption(flags, colorId, colors, darkColors, requiredChannelMinBoostLevel, requiredGroupMinBoostLevel): let isHidden = (flags & (1 << 0)) != 0 let mappedColors = colors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:)) let mappedDarkColors = darkColors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:)) if let mappedColors = mappedColors { - mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: mappedColors, dark: mappedDarkColors, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel))) + mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: mappedColors, dark: mappedDarkColors, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel, requiredGroupMinBoostLevel: requiredGroupMinBoostLevel))) } else if colorId >= 0 && colorId <= 6 { let staticMap: [UInt32] = [ 0xcc5049, @@ -290,7 +298,7 @@ private extension EngineAvailableColorOptions { ] let colorPack = MultiColorPack(colors: [staticMap[Int(colorId)]]) let defaultColors = EngineAvailableColorOptions.ColorOption(palette: colorPack, background: colorPack, stories: nil) - mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: defaultColors, dark: nil, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel))) + mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: defaultColors, dark: nil, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel, requiredGroupMinBoostLevel: requiredGroupMinBoostLevel))) } } } diff --git a/submodules/TelegramCore/Sources/State/SavedMessageTags.swift b/submodules/TelegramCore/Sources/State/SavedMessageTags.swift index d7a232fc45..3d0cb4e8f4 100644 --- a/submodules/TelegramCore/Sources/State/SavedMessageTags.swift +++ b/submodules/TelegramCore/Sources/State/SavedMessageTags.swift @@ -132,6 +132,95 @@ func _internal_setSavedMessageTags(transaction: Transaction, savedMessageTags: S } } +func managedSynchronizeSavedMessageTags(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal { + let poll = Signal { subscriber in + let key: PostboxViewKey = .pendingMessageActions(type: .updateReaction) + let waitForApplySignal: Signal = postbox.combinedView(keys: [key]) + |> map { views -> Bool in + guard let view = views.views[key] as? PendingMessageActionsView else { + return false + } + + for entry in view.entries { + if entry.id.peerId == accountPeerId { + return false + } + } + + return true + } + |> filter { $0 } + |> take(1) + |> ignoreValues + + let signal: Signal = _internal_savedMessageTags(postbox: postbox) + |> mapToSignal { current in + return (network.request(Api.functions.messages.getSavedReactionTags(flags: 0, peer: nil, hash: current?.hash ?? 0)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .complete() + } + + switch result { + case .savedReactionTagsNotModified: + return .complete() + case let .savedReactionTags(tags, hash): + var customFileIds: [Int64] = [] + + var parsedTags: [SavedMessageTags.Tag] = [] + for tag in tags { + switch tag { + case let .savedReactionTag(_, reaction, title, count): + guard let reaction = MessageReaction.Reaction(apiReaction: reaction) else { + continue + } + parsedTags.append(SavedMessageTags.Tag( + reaction: reaction, + title: title, + count: Int(count) + )) + + if case let .custom(fileId) = reaction { + customFileIds.append(fileId) + } + } + } + + let savedMessageTags = SavedMessageTags( + hash: hash, + tags: parsedTags + ) + + return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: customFileIds) + |> mapToSignal { _ -> Signal in + return postbox.transaction { transaction in + _internal_setSavedMessageTags(transaction: transaction, savedMessageTags: savedMessageTags) + } + |> ignoreValues + } + } + }) + } + + return (waitForApplySignal |> then(signal)).start(completed: { + subscriber.putCompletion() + }) + } + + return ( + poll + |> then( + .complete() + |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()) + ) + ) + |> restart +} + func _internal_setSavedMessageTagTitle(account: Account, reaction: MessageReaction.Reaction, title: String?) -> Signal { return account.postbox.transaction { transaction -> Void in let value = _internal_savedMessageTags(transaction: transaction) ?? SavedMessageTags(hash: 0, tags: []) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 51506fd6da..ebac41c4cf 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 173 + return 174 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 6d559c42e3..d3091dad12 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index b1eedff4e5..87f132c4ca 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, id, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return MessageId(peerId: peerId, namespace: namespace, id: id) case let .messageEmpty(_, id, peerId): @@ -132,7 +132,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -145,7 +145,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, date, _, _): return date @@ -156,7 +156,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -165,7 +165,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_BoostCountMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_BoostCountMessageAttribute.swift new file mode 100644 index 0000000000..4305845d64 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_BoostCountMessageAttribute.swift @@ -0,0 +1,20 @@ +import Foundation +import Postbox + +public class BoostCountMessageAttribute: MessageAttribute { + public let count: Int + + public var associatedMessageIds: [MessageId] = [] + + public init(count: Int) { + self.count = count + } + + required public init(decoder: PostboxDecoder) { + self.count = Int(decoder.decodeInt32ForKey("c", orElse: 0)) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(Int32(self.count), forKey: "c") + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index c4d1462963..cfef517df3 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -260,6 +260,9 @@ public final class CachedChannelData: CachedPeerData { public let membersHidden: EnginePeerCachedInfoItem public let viewForumAsMessages: EnginePeerCachedInfoItem public let wallpaper: TelegramWallpaper? + public let boostsToUnrestrict: Int32? + public let appliedBoosts: Int32? + public let emojiPack: StickerPackCollectionInfo? public let peerIds: Set public let messageIds: Set @@ -301,6 +304,9 @@ public final class CachedChannelData: CachedPeerData { self.membersHidden = .unknown self.viewForumAsMessages = .unknown self.wallpaper = nil + self.boostsToUnrestrict = nil + self.appliedBoosts = nil + self.emojiPack = nil } public init( @@ -334,7 +340,10 @@ public final class CachedChannelData: CachedPeerData { allowedReactions: EnginePeerCachedInfoItem, membersHidden: EnginePeerCachedInfoItem, viewForumAsMessages: EnginePeerCachedInfoItem, - wallpaper: TelegramWallpaper? + wallpaper: TelegramWallpaper?, + boostsToUnrestrict: Int32?, + appliedBoosts: Int32?, + emojiPack: StickerPackCollectionInfo? ) { self.isNotAccessible = isNotAccessible self.flags = flags @@ -367,6 +376,9 @@ public final class CachedChannelData: CachedPeerData { self.membersHidden = membersHidden self.viewForumAsMessages = viewForumAsMessages self.wallpaper = wallpaper + self.boostsToUnrestrict = boostsToUnrestrict + self.appliedBoosts = appliedBoosts + self.emojiPack = emojiPack var peerIds = Set() for botInfo in botInfos { @@ -394,129 +406,141 @@ 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } public func withUpdatedInvitedOn(_ invitedOn: 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: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } public func withUpdatedInviteRequestsPending(_ inviteRequestsPending: 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: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } public func withUpdatedSendAsPeerId(_ sendAsPeerId: 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } public func withUpdatedAllowedReactions(_ allowedReactions: EnginePeerCachedInfoItem) -> 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } public func withUpdatedMembersHidden(_ membersHidden: EnginePeerCachedInfoItem) -> 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } public func withUpdatedViewForumAsMessages(_ viewForumAsMessages: EnginePeerCachedInfoItem) -> 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: viewForumAsMessages, wallpaper: self.wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } public func withUpdatedWallpaper(_ wallpaper: TelegramWallpaper?) -> 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: wallpaper) + 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } + public func withUpdatedBoostsToUnrestrict(_ boostsToUnrestrict: 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: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) + } + + public func withUpdatedAppliedBoosts(_ appliedBoosts: 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: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: appliedBoosts, emojiPack: self.emojiPack) + } + + public func withUpdatedEmojiPack(_ emojiPack: 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: 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, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: emojiPack) + } + public init(decoder: PostboxDecoder) { self.isNotAccessible = decoder.decodeInt32ForKey("isNotAccessible", orElse: 0) != 0 self.flags = CachedChannelFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0)) @@ -632,6 +656,16 @@ public final class CachedChannelData: CachedPeerData { } } + self.boostsToUnrestrict = decoder.decodeOptionalInt32ForKey("bu") + + self.appliedBoosts = decoder.decodeOptionalInt32ForKey("ab") + + if let emojiPack = decoder.decodeObjectForKey("ep", decoder: { StickerPackCollectionInfo(decoder: $0) }) as? StickerPackCollectionInfo { + self.emojiPack = emojiPack + } else { + self.emojiPack = nil + } + self.peerIds = peerIds var messageIds = Set() @@ -796,6 +830,24 @@ public final class CachedChannelData: CachedPeerData { } else { encoder.encodeNil(forKey: "wp") } + + if let boostsToUnrestrict = self.boostsToUnrestrict { + encoder.encodeInt32(boostsToUnrestrict, forKey: "bu") + } else { + encoder.encodeNil(forKey: "bu") + } + + if let appliedBoosts = self.appliedBoosts { + encoder.encodeInt32(appliedBoosts, forKey: "ab") + } else { + encoder.encodeNil(forKey: "ab") + } + + if let emojiPack = self.emojiPack { + encoder.encodeObject(emojiPack, forKey: "ep") + } else { + encoder.encodeNil(forKey: "ep") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -922,10 +974,23 @@ public final class CachedChannelData: CachedPeerData { if other.viewForumAsMessages != self.viewForumAsMessages { return false } + if other.wallpaper != self.wallpaper { return false } + if other.boostsToUnrestrict != self.boostsToUnrestrict { + return false + } + + if other.appliedBoosts != self.appliedBoosts { + return false + } + + if other.emojiPack != self.emojiPack { + return false + } + return true } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizeableChatInputState.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizeableChatInputState.swift index 13dcc3e32a..c802fb9a4c 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizeableChatInputState.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizeableChatInputState.swift @@ -66,19 +66,56 @@ public struct SynchronizeableChatInputState: Codable, Equatable { class InternalChatInterfaceState: Codable { let synchronizeableInputState: SynchronizeableChatInputState? let historyScrollMessageIndex: MessageIndex? + let mediaDraftState: MediaDraftState? let opaqueData: Data? init( synchronizeableInputState: SynchronizeableChatInputState?, historyScrollMessageIndex: MessageIndex?, + mediaDraftState: MediaDraftState?, opaqueData: Data? ) { self.synchronizeableInputState = synchronizeableInputState self.historyScrollMessageIndex = historyScrollMessageIndex + self.mediaDraftState = mediaDraftState self.opaqueData = opaqueData } } +public struct MediaDraftState: Codable, Equatable { + public let contentType: EngineChatList.MediaDraftContentType + public let timestamp: Int32 + + public init(contentType: EngineChatList.MediaDraftContentType, timestamp: Int32) { + self.contentType = contentType + self.timestamp = timestamp + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.contentType = EngineChatList.MediaDraftContentType(rawValue: try container.decode(Int32.self, forKey: "t")) ?? .audio + self.timestamp = (try? container.decode(Int32.self, forKey: "s")) ?? 0 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(self.contentType.rawValue, forKey: "t") + try container.encode(self.timestamp, forKey: "s") + } + + public static func ==(lhs: MediaDraftState, rhs: MediaDraftState) -> Bool { + if lhs.contentType != rhs.contentType { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + return true + } +} + func _internal_updateChatInputState(transaction: Transaction, peerId: PeerId, threadId: Int64?, inputState: SynchronizeableChatInputState?) { var previousState: InternalChatInterfaceState? if let threadId = threadId { @@ -90,14 +127,27 @@ func _internal_updateChatInputState(transaction: Transaction, peerId: PeerId, th previousState = (try? AdaptedPostboxDecoder().decode(InternalChatInterfaceState.self, from: data)) } } + + var overrideChatTimestamp: Int32? + if let inputState = inputState { + overrideChatTimestamp = inputState.timestamp + } + + if let mediaDraftState = previousState?.mediaDraftState { + if let current = overrideChatTimestamp, mediaDraftState.timestamp < current { + } else { + overrideChatTimestamp = mediaDraftState.timestamp + } + } if let updatedStateData = try? AdaptedPostboxEncoder().encode(InternalChatInterfaceState( synchronizeableInputState: inputState, historyScrollMessageIndex: previousState?.historyScrollMessageIndex, + mediaDraftState: previousState?.mediaDraftState, opaqueData: previousState?.opaqueData )) { let storedState = StoredPeerChatInterfaceState( - overrideChatTimestamp: inputState?.timestamp, + overrideChatTimestamp: overrideChatTimestamp, historyScrollMessageIndex: previousState?.historyScrollMessageIndex, associatedMessageIds: (inputState?.replySubject?.messageId).flatMap({ [$0] }) ?? [], data: updatedStateData diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index f0668d34e9..6dbb501727 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -126,6 +126,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case giveawayLaunched case joinedChannel case giveawayResults(winners: Int32, unclaimed: Int32) + case boostsApplied(boosts: Int32) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -232,6 +233,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .joinedChannel case 39: self = .giveawayResults(winners: decoder.decodeInt32ForKey("winners", orElse: 0), unclaimed: decoder.decodeInt32ForKey("unclaimed", orElse: 0)) + case 40: + self = .boostsApplied(boosts: decoder.decodeInt32ForKey("boosts", orElse: 0)) default: self = .unknown } @@ -451,6 +454,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeInt32(39, forKey: "_rawValue") encoder.encodeInt32(winners, forKey: "winners") encoder.encodeInt32(unclaimed, forKey: "unclaimed") + case let .boostsApplied(boosts): + encoder.encodeInt32(40, forKey: "_rawValue") + encoder.encodeInt32(boosts, forKey: "boosts") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ContactManagement.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ContactManagement.swift index b009f95ebf..6e4bae85f9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ContactManagement.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ContactManagement.swift @@ -127,6 +127,39 @@ func _internal_deleteContactPeerInteractively(account: Account, peerId: PeerId) |> switchToLatest } +func _internal_deleteContacts(account: Account, peerIds: [PeerId]) -> Signal { + return account.postbox.transaction { transaction -> Signal in + let users = peerIds.compactMap { transaction.getPeer($0) } + let inputUsers: [Api.InputUser] = users.compactMap { apiInputUser($0) } + if !inputUsers.isEmpty { + return account.network.request(Api.functions.contacts.deleteContacts(id: inputUsers)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates = updates { + account.stateManager.addUpdates(updates) + } + return account.postbox.transaction { transaction -> Void in + for user in users { + if let user = user as? TelegramUser { + _internal_updatePeerIsContact(transaction: transaction, user: user, isContact: false) + } + } + + let updatedContactPeerIds = transaction.getContactPeerIds().filter { !peerIds.contains($0) } + transaction.replaceContactPeerIds(updatedContactPeerIds) + } + } + |> ignoreValues + } else { + return .complete() + } + } + |> switchToLatest +} + func _internal_deleteAllContacts(account: Account) -> Signal { return account.postbox.transaction { transaction -> [Api.InputUser] in return transaction.getContactPeerIds().compactMap(transaction.getPeer).compactMap({ apiInputUser($0) }).compactMap({ $0 }) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift index 913d5b67da..27dca9b700 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift @@ -13,6 +13,10 @@ public extension TelegramEngine { public func deleteContactPeerInteractively(peerId: PeerId) -> Signal { return _internal_deleteContactPeerInteractively(account: self.account, peerId: peerId) } + + public func deleteContacts(peerIds: [PeerId]) -> Signal { + return _internal_deleteContacts(account: self.account, peerIds: peerIds) + } public func deleteAllContacts() -> Signal { return _internal_deleteAllContacts(account: self.account) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 9b3049aac8..da715c2d31 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -547,6 +547,33 @@ public extension TelegramEngine.EngineData.Item { } } + public struct EmojiPack: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public typealias Result = StickerPackCollectionInfo? + + fileprivate var id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + var key: PostboxViewKey { + return .cachedPeerData(peerId: self.id) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? CachedPeerDataView else { + preconditionFailure() + } + guard let cachedData = view.cachedPeerData as? CachedChannelData else { + return nil + } + return cachedData.emojiPack + } + } + public struct AllowedReactions: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = EnginePeerCachedInfoItem diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index 8836ceab30..34b3779c6b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -45,6 +45,11 @@ public final class EngineChatList: Equatable { self.isUnread = isUnread } } + + public enum MediaDraftContentType: Int32 { + case audio + case video + } public final class Item: Equatable { public enum Id: Hashable { @@ -131,6 +136,7 @@ public final class EngineChatList: Equatable { public let storyStats: StoryStats? public let displayAsTopicList: Bool public let isPremiumRequiredToMessage: Bool + public let mediaDraftContentType: EngineChatList.MediaDraftContentType? public init( id: Id, @@ -151,7 +157,8 @@ public final class EngineChatList: Equatable { autoremoveTimeout: Int32?, storyStats: StoryStats?, displayAsTopicList: Bool, - isPremiumRequiredToMessage: Bool + isPremiumRequiredToMessage: Bool, + mediaDraftContentType: EngineChatList.MediaDraftContentType? ) { self.id = id self.index = index @@ -172,6 +179,7 @@ public final class EngineChatList: Equatable { self.storyStats = storyStats self.displayAsTopicList = displayAsTopicList self.isPremiumRequiredToMessage = isPremiumRequiredToMessage + self.mediaDraftContentType = mediaDraftContentType } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -232,6 +240,9 @@ public final class EngineChatList: Equatable { if lhs.isPremiumRequiredToMessage != rhs.isPremiumRequiredToMessage { return false } + if lhs.mediaDraftContentType != rhs.mediaDraftContentType { + return false + } return true } } @@ -467,11 +478,13 @@ extension EngineChatList.Item { } var draft: EngineChatList.Draft? + var mediaDraftContentType: EngineChatList.MediaDraftContentType? if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp { if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { if let text = opaqueState.synchronizeableInputState?.text { draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? []) } + mediaDraftContentType = opaqueState.mediaDraftState?.contentType } } @@ -536,7 +549,8 @@ extension EngineChatList.Item { autoremoveTimeout: autoremoveTimeout, storyStats: entryData.storyStats, displayAsTopicList: displayAsTopicList, - isPremiumRequiredToMessage: isPremiumRequiredToMessage + isPremiumRequiredToMessage: isPremiumRequiredToMessage, + mediaDraftContentType: mediaDraftContentType ) case .HoleEntry: return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index 718d9720e4..3ece75f732 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -91,6 +91,7 @@ public enum AdminLogEventAction { case changeProfileColor(prevColor: PeerNameColor?, prevIcon: Int64?, newColor: PeerNameColor?, newIcon: Int64?) case changeWallpaper(prev: TelegramWallpaper?, new: TelegramWallpaper?) case changeStatus(prev: PeerEmojiStatus?, new: PeerEmojiStatus?) + case changeEmojiPack(prev: StickerPackReference?, new: StickerPackReference?) } public enum ChannelAdminLogEventError { @@ -414,6 +415,8 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net action = .changeWallpaper(prev: prev, new: new) case let .channelAdminLogEventActionChangeEmojiStatus(prevValue, newValue): action = .changeStatus(prev: PeerEmojiStatus(apiStatus: prevValue), new: PeerEmojiStatus(apiStatus: newValue)) + case let .channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset, newStickerset): + action = .changeEmojiPack(prev: StickerPackReference(apiInputSet: prevStickerset), new: StickerPackReference(apiInputSet: newStickerset)) } let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) if let action = action { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift index 0813017891..ddeea363e8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift @@ -183,3 +183,33 @@ func _internal_updateDefaultChannelMemberBannedRights(account: Account, peerId: |> switchToLatest } +func _internal_updateChannelBoostsToUnlockRestrictions(account: Account, peerId: PeerId, boosts: Int32) -> Signal { + return account.postbox.transaction { transaction -> Signal in + guard let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) else { + return .complete() + } + return account.network.request(Api.functions.channels.setBoostsToUnblockRestrictions(channel: inputChannel, boosts: boosts)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .complete() + } + account.stateManager.addUpdates(result) + return account.postbox.transaction { transaction -> Void in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in + if let cachedData = cachedData as? CachedChannelData { + return cachedData.withUpdatedBoostsToUnrestrict(boosts) + } + return cachedData + }) + } + |> ignoreValues + } + } + |> switchToLatest +} + + diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift index 3110230154..082d0ca9d7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerSpecificStickerPack.swift @@ -46,3 +46,35 @@ func _internal_peerSpecificStickerPack(postbox: Postbox, network: Network, peerI return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: false)) } } + + +func _internal_peerSpecificEmojiPack(postbox: Postbox, network: Network, peerId: PeerId) -> Signal { + if peerId.namespace == Namespaces.Peer.CloudChannel { + let signal: Signal<(WrappedStickerPackCollectionInfo, Bool), NoError> = postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)]) + |> map { view -> (WrappedStickerPackCollectionInfo, Bool) in + let dataView = view.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView + return (WrappedStickerPackCollectionInfo(info: (dataView?.cachedPeerData as? CachedChannelData)?.emojiPack), (dataView?.cachedPeerData as? CachedChannelData)?.flags.contains(.canSetStickerSet) ?? false) + } + |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + return lhs.0 == rhs.0 && lhs.1 == rhs.1 + }) + + return signal + |> mapToSignal { info, canInstall -> Signal in + if let info = info.info { + return _internal_cachedStickerPack(postbox: postbox, network: network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceRemote: false) + |> map { result -> PeerSpecificStickerPackData in + if case let .result(info, items, _) = result { + return PeerSpecificStickerPackData(packInfo: (info, items), canSetup: canInstall) + } else { + return PeerSpecificStickerPackData(packInfo: nil, canSetup: canInstall) + } + } + } else { + return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: canInstall)) + } + } + } else { + return .single(PeerSpecificStickerPackData(packInfo: nil, canSetup: false)) + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index eecb8dc996..ee8a1629fe 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -14,15 +14,18 @@ public typealias EngineStringIndexTokenTransliteration = StringIndexTokenTransli public final class OpaqueChatInterfaceState { public let opaqueData: Data? public let historyScrollMessageIndex: MessageIndex? + public let mediaDraftState: MediaDraftState? public let synchronizeableInputState: SynchronizeableChatInputState? public init( opaqueData: Data?, historyScrollMessageIndex: MessageIndex?, + mediaDraftState: MediaDraftState?, synchronizeableInputState: SynchronizeableChatInputState? ) { self.opaqueData = opaqueData self.historyScrollMessageIndex = historyScrollMessageIndex + self.mediaDraftState = mediaDraftState self.synchronizeableInputState = synchronizeableInputState } } @@ -349,6 +352,10 @@ public extension TelegramEngine { public func updateDefaultChannelMemberBannedRights(peerId: PeerId, rights: TelegramChatBannedRights) -> Signal { return _internal_updateDefaultChannelMemberBannedRights(account: self.account, peerId: peerId, rights: rights) } + + public func updateChannelBoostsToUnlockRestrictions(peerId: PeerId, boosts: Int32) -> Signal { + return _internal_updateChannelBoostsToUnlockRestrictions(account: self.account, peerId: peerId, boosts: boosts) + } public func createChannel(title: String, description: String?, username: String? = nil) -> Signal { return _internal_createChannel(account: self.account, title: title, description: description, username: username) @@ -416,6 +423,10 @@ public extension TelegramEngine { public func updateGroupSpecificStickerset(peerId: PeerId, info: StickerPackCollectionInfo?) -> Signal { return _internal_updateGroupSpecificStickerset(postbox: self.account.postbox, network: self.account.network, peerId: peerId, info: info) } + + public func updateGroupSpecificEmojiset(peerId: PeerId, info: StickerPackCollectionInfo?) -> Signal { + return _internal_updateGroupSpecificEmojiset(postbox: self.account.postbox, network: self.account.network, peerId: peerId, info: info) + } public func joinChannel(peerId: PeerId, hash: String?) -> Signal { return _internal_joinChannel(account: self.account, peerId: peerId, hash: hash) @@ -459,6 +470,10 @@ public extension TelegramEngine { public func peerSpecificStickerPack(peerId: PeerId) -> Signal { return _internal_peerSpecificStickerPack(postbox: self.account.postbox, network: self.account.network, peerId: peerId) } + + public func peerSpecificEmojiPack(peerId: PeerId) -> Signal { + return _internal_peerSpecificEmojiPack(postbox: self.account.postbox, network: self.account.network, peerId: peerId) + } public func addRecentlySearchedPeer(peerId: PeerId) -> Signal { return _internal_addRecentlySearchedPeer(postbox: self.account.postbox, peerId: peerId) @@ -863,6 +878,7 @@ public extension TelegramEngine { return OpaqueChatInterfaceState( opaqueData: internalState.opaqueData, historyScrollMessageIndex: internalState.historyScrollMessageIndex, + mediaDraftState: internalState.mediaDraftState, synchronizeableInputState: internalState.synchronizeableInputState ) } @@ -873,6 +889,7 @@ public extension TelegramEngine { guard let data = try? AdaptedPostboxEncoder().encode(InternalChatInterfaceState( synchronizeableInputState: state.synchronizeableInputState, historyScrollMessageIndex: state.historyScrollMessageIndex, + mediaDraftState: state.mediaDraftState, opaqueData: state.opaqueData )) else { return @@ -881,9 +898,21 @@ public extension TelegramEngine { #if DEBUG let _ = try! AdaptedPostboxDecoder().decode(InternalChatInterfaceState.self, from: data) #endif + + var overrideChatTimestamp: Int32? + if let inputState = state.synchronizeableInputState { + overrideChatTimestamp = inputState.timestamp + } + + if let mediaDraftState = state.mediaDraftState { + if let current = overrideChatTimestamp, mediaDraftState.timestamp < current { + } else { + overrideChatTimestamp = mediaDraftState.timestamp + } + } let storedState = StoredPeerChatInterfaceState( - overrideChatTimestamp: state.synchronizeableInputState?.timestamp, + overrideChatTimestamp: overrideChatTimestamp, historyScrollMessageIndex: state.historyScrollMessageIndex, associatedMessageIds: (state.synchronizeableInputState?.replySubject?.messageId).flatMap({ [$0] }) ?? [], data: data @@ -1367,6 +1396,7 @@ public func _internal_decodeStoredChatInterfaceState(state: StoredPeerChatInterf return OpaqueChatInterfaceState( opaqueData: internalState.opaqueData, historyScrollMessageIndex: internalState.historyScrollMessageIndex, + mediaDraftState: internalState.mediaDraftState, synchronizeableInputState: internalState.synchronizeableInputState ) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 35d7cb1735..81af3adfb1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -452,14 +452,14 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee switch result { case let .chatFull(fullChat, chats, users): switch fullChat { - case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)]) case .chatFull: break } switch fullChat { - case let .channelFull(flags, flags2, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, _, defaultSendAs, allowedReactions, _, wallpaper): + case let .channelFull(flags, flags2, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, _, defaultSendAs, allowedReactions, _, wallpaper, appliedBoosts, boostsUnrestrict, emojiSet): var channelFlags = CachedChannelFlags() if (flags & (1 << 3)) != 0 { channelFlags.insert(.canDisplayParticipants) @@ -588,6 +588,22 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } let photo = telegramMediaImageFromApiPhoto(chatPhoto) + + let emojiPack: StickerPackCollectionInfo? = emojiSet.flatMap { apiSet -> StickerPackCollectionInfo in + let namespace: ItemCollectionId.Namespace + switch apiSet { + case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _): + if (flags & (1 << 3)) != 0 { + namespace = Namespaces.ItemCollection.CloudMaskPacks + } else if (flags & (1 << 7)) != 0 { + namespace = Namespaces.ItemCollection.CloudEmojiPacks + } else { + namespace = Namespaces.ItemCollection.CloudStickerPacks + } + } + + return StickerPackCollectionInfo(apiSet: apiSet, namespace: namespace) + } var minAvailableMessageIdUpdated = false transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in @@ -658,6 +674,9 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedMembersHidden(.known(PeerMembersHidden(value: membersHidden))) .withUpdatedViewForumAsMessages(.known(forumViewAsMessages)) .withUpdatedWallpaper(wallpaper) + .withUpdatedBoostsToUnrestrict(boostsUnrestrict) + .withUpdatedAppliedBoosts(appliedBoosts) + .withUpdatedEmojiPack(emojiPack) }) if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateGroupSpecificStickerset.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateGroupSpecificStickerset.swift index b2cb591709..047910c571 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateGroupSpecificStickerset.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateGroupSpecificStickerset.swift @@ -40,3 +40,41 @@ func _internal_updateGroupSpecificStickerset(postbox: Postbox, network: Network, return .complete() } } + + +public enum UpdateGroupSpecificEmojisetError { + case generic +} + +func _internal_updateGroupSpecificEmojiset(postbox: Postbox, network: Network, peerId: PeerId, info: StickerPackCollectionInfo?) -> Signal { + return postbox.loadedPeerWithId(peerId) + |> castError(UpdateGroupSpecificEmojisetError.self) + |> mapToSignal { peer -> Signal in + let inputStickerset: Api.InputStickerSet + if let info = info { + inputStickerset = Api.InputStickerSet.inputStickerSetShortName(shortName: info.shortName) + } else { + inputStickerset = Api.InputStickerSet.inputStickerSetEmpty + } + if let inputChannel = apiInputChannel(peer) { + return network.request(Api.functions.channels.setEmojiStickers(channel: inputChannel, stickerset: inputStickerset)) + |> mapError { _ -> UpdateGroupSpecificEmojisetError in + return .generic + } + |> mapToSignal { value -> Signal in + switch value { + case .boolTrue: + return postbox.transaction { transaction -> Void in + return transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current -> CachedPeerData? in + return (current as? CachedChannelData)?.withUpdatedEmojiPack(info) + }) + } + |> castError(UpdateGroupSpecificEmojisetError.self) + default: + return .complete() + } + } + } + return .complete() + } +} diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index bea3e90919..4ebdad3bab 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -197,6 +197,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case outgoingVideoMessagePlayOnceTip = 63 case dismissedMessageTagsBadge = 64 case savedMessageTagLabelSuggestion = 65 + case dismissedLastSeenBadge = 66 + case dismissedMessagePrivacyBadge = 67 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -514,6 +516,14 @@ private struct ApplicationSpecificNoticeKeys { static func savedMessageTagLabelSuggestion() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessageTagLabelSuggestion.key) } + + static func dismissedLastSeenBadge() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedLastSeenBadge.key) + } + + static func dismissedMessagePrivacyBadge() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedMessagePrivacyBadge.key) + } } public struct ApplicationSpecificNotice { @@ -2112,6 +2122,7 @@ public struct ApplicationSpecificNotice { |> take(1) } + public static func getSavedMessageTagLabelSuggestion(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.savedMessageTagLabelSuggestion())?.get(ApplicationSpecificCounterNotice.self) { @@ -2130,7 +2141,7 @@ public struct ApplicationSpecificNotice { } let previousValue = currentValue currentValue += Int32(count) - + if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.savedMessageTagLabelSuggestion(), entry) } @@ -2138,4 +2149,46 @@ public struct ApplicationSpecificNotice { return Int(previousValue) } } + + public static func setDismissedLastSeenBadge(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedLastSeenBadge(), entry) + } + } + |> ignoreValues + } + + public static func dismissedLastSeenBadge(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedLastSeenBadge()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + |> take(1) + } + + public static func setDismissedMessagePrivacyBadge(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedMessagePrivacyBadge(), entry) + } + } + |> ignoreValues + } + + public static func dismissedMessagePrivacyBadge(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedMessagePrivacyBadge()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + |> take(1) + } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index fceba2a659..8f50f597dd 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -124,6 +124,7 @@ public enum PresentationResourceKey: Int32 { case chatTitleLockIcon case chatTitleMuteIcon case chatPanelLockIcon + case chatPanelBoostIcon case chatBubbleVerticalLineIncomingImage case chatBubbleVerticalLineOutgoingImage diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 4d907664ce..64cb8c0ef9 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -84,6 +84,12 @@ public struct PresentationResourcesChat { }) } + public static func chatPanelBoostIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatPanelBoostIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Premium/BoostChannel"), color: theme.chat.inputPanel.panelControlAccentColor) + }) + } + public static func chatTitleMuteIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatTitleMuteIcon.rawValue, { theme in return generateImage(CGSize(width: 9.0, height: 9.0), rotatedContext: { size, context in diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 67873fb57e..b897145cae 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -278,7 +278,7 @@ public struct PresentationResourcesItemList { public static func addBoostsIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListAddBoostsIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Premium/AddBoosts"), color: theme.list.itemAccentColor) + return generateTintedImage(image: UIImage(bundleImageName: "Premium/Gift"), color: theme.list.itemAccentColor) }) } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index ed1e6f4ede..861a0885a5 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -937,7 +937,12 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = NSAttributedString(string: strings.Notification_GiftLink, font: titleFont, textColor: primaryTextColor) } case .giveawayLaunched: - let resultTitleString = strings.Notification_GiveawayStarted(compactAuthorName) + var isGroup = false + let messagePeer = message.peers[message.id.peerId] + if let channel = messagePeer as? TelegramChannel, case .group = channel.info { + isGroup = true + } + let resultTitleString = isGroup ? strings.Notification_GiveawayStartedGroup(compactAuthorName) : strings.Notification_GiveawayStarted(compactAuthorName) attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) case .joinedChannel: attributedString = NSAttributedString(string: strings.Notification_ChannelJoinedByYou, font: titleBoldFont, textColor: primaryTextColor) @@ -954,6 +959,25 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } else { attributedString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResults(winners), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) } + case let .boostsApplied(boosts): + if message.author?.id == accountPeerId { + if boosts == 1 { + attributedString = NSAttributedString(string: strings.Notification_Boost_SingleYou, font: titleFont, textColor: primaryTextColor) + } else { + let boostsString = strings.Notification_Boost_Times(boosts) + attributedString = NSAttributedString(string: strings.Notification_Boost_MultipleYou(boostsString).string, font: titleFont, textColor: primaryTextColor) + } + } else { + let peerName = message.author?.compactDisplayTitle ?? "" + if boosts == 1 { + let resultTitleString = strings.Notification_Boost_Single(peerName) + attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) + } else { + let boostsString = strings.Notification_Boost_Times(boosts) + let resultTitleString = strings.Notification_Boost_Multiple(peerName, boostsString) + attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) + } + } case .unknown: attributedString = nil } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 86b465c4e0..3d22f9ef11 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -426,6 +426,7 @@ swift_library( "//submodules/TelegramUI/Components/VideoMessageCameraScreen", "//submodules/TelegramUI/Components/MediaScrubberComponent", "//submodules/TelegramUI/Components/Chat/ChatShareMessageTagView", + "//submodules/AudioWaveform", "//submodules/PromptUI", "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/Chat/TopMessageReactions", diff --git a/submodules/TelegramUI/Components/AudioWaveformNode/BUILD b/submodules/TelegramUI/Components/AudioWaveformNode/BUILD index 7b6c35efc3..e1dce2c687 100644 --- a/submodules/TelegramUI/Components/AudioWaveformNode/BUILD +++ b/submodules/TelegramUI/Components/AudioWaveformNode/BUILD @@ -12,7 +12,7 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/ChatPresentationInterfaceState", + "//submodules/AudioWaveform", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/AudioWaveformNode/Sources/AudioWaveformNode.swift b/submodules/TelegramUI/Components/AudioWaveformNode/Sources/AudioWaveformNode.swift index f1073863bf..06eb8e589c 100644 --- a/submodules/TelegramUI/Components/AudioWaveformNode/Sources/AudioWaveformNode.swift +++ b/submodules/TelegramUI/Components/AudioWaveformNode/Sources/AudioWaveformNode.swift @@ -2,7 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import ChatPresentationInterfaceState +import AudioWaveform private final class AudioWaveformNodeParameters: NSObject { let waveform: AudioWaveform? diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index fbeb745242..28692a32ca 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -428,6 +428,7 @@ final class AvatarEditorScreenComponent: Component { groupId: "emoji", title: "Emoji", subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -448,6 +449,7 @@ final class AvatarEditorScreenComponent: Component { groupId: "stickers", title: "Stickers", subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -505,6 +507,7 @@ final class AvatarEditorScreenComponent: Component { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -538,6 +541,7 @@ final class AvatarEditorScreenComponent: Component { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index f1888b987c..087597bc24 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -746,6 +746,7 @@ public final class ChatInlineSearchResultsListComponent: Component { hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, + mediaDraftContentType: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 145026a6cf..0e397b8d9c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -481,7 +481,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var emojiFile: TelegramMediaFile? var emojiString: String? - if messageIsElligibleForLargeCustomEmoji(item.message) || messageIsElligibleForLargeEmoji(item.message) { + if messageIsEligibleForLargeCustomEmoji(item.message) || messageIsEligibleForLargeEmoji(item.message) { emojiString = item.message.text } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 66ac0277e8..adb7ea06bf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -555,6 +555,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var credibilityButtonNode: HighlightTrackingButtonNode? private var credibilityHighlightNode: ASImageNode? + private var boostBadgeNode: TextNode? + private var boostIconNode: UIImageView? + private var boostCount: Int = 0 + + private var boostButtonNode: HighlightTrackingButtonNode? + private var boostHighlightNode: ASImageNode? + private var closeButtonNode: HighlightTrackingButtonNode? private var closeIconNode: ASImageNode? @@ -1099,6 +1106,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let credibilityButtonNode = strongSelf.credibilityButtonNode, credibilityButtonNode.frame.contains(point) { return .fail } + + if let boostButtonNode = strongSelf.boostButtonNode, boostButtonNode.frame.contains(point) { + return .fail + } if let nameNode = strongSelf.nameNode, nameNode.frame.contains(point) { if let item = strongSelf.item { @@ -1264,6 +1275,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let authorNameLayout = TextNode.asyncLayout(self.nameNode) let viaMeasureLayout = TextNode.asyncLayout(self.viaMeasureNode) let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode) + let boostBadgeLayout = TextNode.asyncLayout(self.boostBadgeNode) let threadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode) let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) let replyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) @@ -1288,6 +1300,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI authorNameLayout: authorNameLayout, viaMeasureLayout: viaMeasureLayout, adminBadgeLayout: adminBadgeLayout, + boostBadgeLayout: boostBadgeLayout, threadInfoLayout: threadInfoLayout, forwardInfoLayout: forwardInfoLayout, replyInfoLayout: replyInfoLayout, @@ -1307,6 +1320,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), viaMeasureLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), + boostBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode), forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode), @@ -1324,7 +1338,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let fontSize = floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0) let nameFont = Font.semibold(fontSize) - let inlineBotPrefixFont = Font.regular(fontSize) + let inlineBotPrefixFont = Font.regular(fontSize - 1.0) + let boostBadgeFont = Font.regular(fontSize - 1.0) let baseWidth = params.width - params.leftInset - params.rightInset @@ -1502,7 +1517,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if !needsShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo { if !item.message.media.isEmpty && !(item.message.media.first is TelegramMediaAction) { needsShareButton = true - } else if author.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(92386307)) { + } else if author.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(92386307)) || author.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(6435149744)) { needsShareButton = true } } @@ -2142,6 +2157,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var nameNodeOriginY: CGFloat = 0.0 var nameNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) var adminNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) + var boostNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) var viaWidth: CGFloat = 0.0 var threadInfoOriginY: CGFloat = 0.0 @@ -2167,6 +2183,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let attributedString: NSAttributedString var adminBadgeString: NSAttributedString? + var boostBadgeString: NSAttributedString? if let authorRank = authorRank { let string: String switch authorRank { @@ -2214,10 +2231,35 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI credibilityIconWidth += 20.0 } } + let adminBadgeSizeAndApply = adminBadgeLayout(TextNodeLayoutArguments(attributedString: adminBadgeString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0, maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - adminNodeSizeApply = (adminBadgeSizeAndApply.0.size, { - return adminBadgeSizeAndApply.1() - }) + if adminBadgeSizeAndApply.0.size.width > 0.0 { + adminNodeSizeApply = (adminBadgeSizeAndApply.0.size, { + return adminBadgeSizeAndApply.1() + }) + } + + var boostCount: Int = 0 + for attribute in item.message.attributes { + if let attribute = attribute as? BoostCountMessageAttribute { + boostCount = attribute.count + } + } + + if boostCount > 1, let authorNameColor = authorNameColor { + boostBadgeString = NSAttributedString(string: "\(boostCount)", font: boostBadgeFont, textColor: authorNameColor) + } + + var boostBadgeWidth: CGFloat = 0.0 + let boostBadgeSizeAndApply = boostBadgeLayout(TextNodeLayoutArguments(attributedString: boostBadgeString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0, maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + if boostBadgeSizeAndApply.0.size.width > 0.0 { + boostNodeSizeApply = (boostBadgeSizeAndApply.0.size, { + return boostBadgeSizeAndApply.1() + }) + boostBadgeWidth += boostBadgeSizeAndApply.0.size.width + 19.0 + } else if boostCount == 1 { + boostBadgeWidth = 14.0 + } let closeButtonWidth: CGFloat = item.message.adAttribute != nil ? 18.0 : 0.0 @@ -2232,7 +2274,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } nameNodeOriginY = headerSize.height - headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + closeButtonWidth + bubbleWidthInsets) + headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets) headerSize.height += nameNodeSizeApply.0.height } @@ -2825,6 +2867,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI layoutConstants: layoutConstants, currentCredibilityIcon: currentCredibilityIcon, adminNodeSizeApply: adminNodeSizeApply, + boostNodeSizeApply: boostNodeSizeApply, contentUpperRightCorner: contentUpperRightCorner, threadInfoSizeApply: threadInfoSizeApply, threadInfoOriginY: threadInfoOriginY, @@ -2878,6 +2921,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI layoutConstants: ChatMessageItemLayoutConstants, currentCredibilityIcon: EmojiStatusComponent.Content?, adminNodeSizeApply: (CGSize, () -> TextNode?), + boostNodeSizeApply: (CGSize, () -> TextNode?), contentUpperRightCorner: CGPoint, threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?), threadInfoOriginY: CGFloat, @@ -3122,18 +3166,132 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI credibilityHighlightNode.frame = credibilityIconFrame.insetBy(dx: -1.0, dy: -1.0) credibilityButtonNode.frame = credibilityIconFrame.insetBy(dx: -2.0, dy: -3.0) - if themeUpdated { + if themeUpdated || credibilityHighlightNode.image == nil { credibilityHighlightNode.image = generateFilledRoundedRectImage(size: CGSize(width: 8.0, height: 8.0), cornerRadius: 4.0, color: nameColor.withAlphaComponent(0.1))?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 4) } } else { strongSelf.credibilityIconView?.removeFromSuperview() strongSelf.credibilityIconView = nil strongSelf.credibilityIconContent = nil + strongSelf.credibilityButtonNode?.removeFromSupernode() + strongSelf.credibilityButtonNode = nil + strongSelf.credibilityHighlightNode?.removeFromSupernode() + strongSelf.credibilityHighlightNode = nil } + var boostCount: Int = 0 + for attribute in item.message.attributes { + if let attribute = attribute as? BoostCountMessageAttribute { + boostCount = attribute.count + } + } + + var rightContentOffset: CGFloat = 0.0 + if let boostBadgeNode = boostNodeSizeApply.1() { + boostBadgeNode.alpha = 0.75 + strongSelf.boostBadgeNode = boostBadgeNode + let boostBadgeFrame = CGRect(origin: CGPoint(x: contentUpperRightCorner.x - layoutConstants.text.bubbleInsets.left - boostNodeSizeApply.0.width, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY + 1.0 - UIScreenPixel), size: boostNodeSizeApply.0) + if boostBadgeNode.supernode == nil { + if !boostBadgeNode.isNodeLoaded { + boostBadgeNode.isUserInteractionEnabled = false + } + strongSelf.clippingNode.addSubnode(boostBadgeNode) + boostBadgeNode.frame = boostBadgeFrame + + if animation.isAnimated { + boostBadgeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } else { + animation.animator.updateFrame(layer: boostBadgeNode.layer, frame: boostBadgeFrame, completion: nil) + } + } else { + strongSelf.boostBadgeNode?.removeFromSupernode() + strongSelf.boostBadgeNode = nil + } + + if boostCount > 0 { + var boostTotalWidth: CGFloat = 22.0 + if boostNodeSizeApply.0.width > 0.0 { + boostTotalWidth += boostNodeSizeApply.0.width + rightContentOffset += boostTotalWidth + } else { + boostTotalWidth -= 6.0 + rightContentOffset += boostTotalWidth - 2.0 + } + + + let boostIconFrame = CGRect(origin: CGPoint(x: contentUpperRightCorner.x - layoutConstants.text.bubbleInsets.left - boostTotalWidth + 4.0, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY + 1.0 - UIScreenPixel - 3.0), size: CGSize(width: boostTotalWidth, height: 22.0)) + + let previousBoostCount = strongSelf.boostCount + + let boostIconNode: UIImageView + let boostButtonNode: HighlightTrackingButtonNode + let boostHighlightNode: ASImageNode + if let currentIcon = strongSelf.boostIconNode, let currentButton = strongSelf.boostButtonNode, let currentHighlight = strongSelf.boostHighlightNode { + boostIconNode = currentIcon + boostButtonNode = currentButton + boostHighlightNode = currentHighlight + } else { + boostIconNode = UIImageView() + boostIconNode.alpha = 0.75 + + strongSelf.clippingNode.view.addSubview(boostIconNode) + strongSelf.boostIconNode = boostIconNode + + boostHighlightNode = ASImageNode() + boostHighlightNode.alpha = 0.0 + boostHighlightNode.displaysAsynchronously = false + boostHighlightNode.isUserInteractionEnabled = false + strongSelf.clippingNode.addSubnode(boostHighlightNode) + strongSelf.boostHighlightNode = boostHighlightNode + + boostButtonNode = HighlightTrackingButtonNode() + boostButtonNode.highligthedChanged = { [weak boostHighlightNode] highlighted in + guard let boostHighlightNode else { + return + } + if highlighted { + boostHighlightNode.layer.removeAnimation(forKey: "opacity") + boostHighlightNode.alpha = 1.0 + } else { + boostHighlightNode.alpha = 0.0 + boostHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } + boostButtonNode.addTarget(strongSelf, action: #selector(strongSelf.boostButtonPressed), forControlEvents: .touchUpInside) + strongSelf.clippingNode.addSubnode(boostButtonNode) + strongSelf.boostButtonNode = boostButtonNode + } + + if boostCount != previousBoostCount { + boostIconNode.image = UIImage(bundleImageName: boostCount == 1 ? "Chat/Message/Boost" : "Chat/Message/Boosts")?.withRenderingMode(.alwaysTemplate) + } + + boostIconNode.tintColor = nameColor + + if let iconSize = boostIconNode.image?.size { + boostIconNode.frame = CGRect(origin: CGPoint(x: boostTotalWidth > 22.0 ? boostIconFrame.minX + 3.0 : boostIconFrame.midX - iconSize.width / 2.0, y: boostIconFrame.midY - iconSize.height / 2.0), size: iconSize) + } + + boostHighlightNode.frame = boostIconFrame + boostButtonNode.frame = boostIconFrame.insetBy(dx: -2.0, dy: -3.0) + + if themeUpdated || boostHighlightNode.image == nil { + boostHighlightNode.image = generateFilledRoundedRectImage(size: CGSize(width: 8.0, height: 8.0), cornerRadius: 4.0, color: nameColor.withAlphaComponent(0.1))?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 4) + } + } else { + strongSelf.boostButtonNode?.removeFromSupernode() + strongSelf.boostButtonNode = nil + strongSelf.boostHighlightNode?.removeFromSupernode() + strongSelf.boostHighlightNode = nil + strongSelf.boostIconNode?.removeFromSuperview() + strongSelf.boostIconNode = nil + } + strongSelf.boostCount = boostCount + if let adminBadgeNode = adminNodeSizeApply.1() { strongSelf.adminBadgeNode = adminBadgeNode - let adminBadgeFrame = CGRect(origin: CGPoint(x: contentUpperRightCorner.x - layoutConstants.text.bubbleInsets.left - adminNodeSizeApply.0.width, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: adminNodeSizeApply.0) + let adminBadgeFrame = CGRect(origin: CGPoint(x: contentUpperRightCorner.x - layoutConstants.text.bubbleInsets.left - rightContentOffset - adminNodeSizeApply.0.width, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY + 1.0 - UIScreenPixel), size: adminNodeSizeApply.0) if adminBadgeNode.supernode == nil { if !adminBadgeNode.isNodeLoaded { adminBadgeNode.isUserInteractionEnabled = false @@ -3145,7 +3303,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI adminBadgeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } else { - //let previousAdminBadgeFrame = adminBadgeNode.frame animation.animator.updateFrame(layer: adminBadgeNode.layer, frame: adminBadgeFrame, completion: nil) } } else { @@ -3236,6 +3393,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.credibilityButtonNode = nil strongSelf.credibilityHighlightNode?.removeFromSupernode() strongSelf.credibilityHighlightNode = nil + strongSelf.boostButtonNode?.removeFromSupernode() + strongSelf.boostButtonNode = nil + strongSelf.boostHighlightNode?.removeFromSupernode() + strongSelf.boostHighlightNode = nil } } @@ -4574,6 +4735,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return credibilityButtonNode.view } + if let boostButtonNode = self.boostButtonNode, boostButtonNode.frame.contains(point) { + return boostButtonNode.view + } + if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) { return shareButtonNode.view } @@ -4998,14 +5163,33 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI @objc private func credibilityButtonPressed() { if let item = self.item, let credibilityIconView = self.credibilityIconView, let iconContent = self.credibilityIconContent, let peer = item.message.author { var emojiFileId: Int64? - if case let .animation(content, _, _, _, _) = iconContent { + switch iconContent { + case let .animation(content, _, _, _, _): emojiFileId = content.fileId.id + case .premium: + break + default: + return } - item.controllerInteraction.openPremiumStatusInfo(peer.id, credibilityIconView, emojiFileId, peer.nameColor ?? .blue) } } + @objc private func boostButtonPressed() { + guard let item = self.item, let peer = item.message.author else { + return + } + + var boostCount: Int = 0 + for attribute in item.message.attributes { + if let attribute = attribute as? BoostCountMessageAttribute { + boostCount = attribute.count + } + } + + item.controllerInteraction.openGroupBoostInfo(peer.id, boostCount) + } + private var playedSwipeToReplyHaptic = false @objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { var offset: CGFloat = 0.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index 1d5cd72204..d04b61be2e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -362,6 +362,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, let participantsText: String let countriesText: String + let isGroup = "".isEmpty + if let giveaway { if giveaway.flags.contains(.onlyNewSubscribers) { if giveaway.channelPeerIds.count > 1 { @@ -373,7 +375,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, if giveaway.channelPeerIds.count > 1 { participantsText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsMany } else { - participantsText = item.presentationData.strings.Chat_Giveaway_Message_Participants + participantsText = isGroup ? item.presentationData.strings.Chat_Giveaway_Message_Group_Participants : item.presentationData.strings.Chat_Giveaway_Message_Participants } } if !giveaway.countries.isEmpty { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD index ac61001594..ff525a9ace 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD @@ -42,6 +42,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/AnimatedCountLabelNode", + "//submodules/AudioWaveform", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index a38eb33ace..b48bb3b249 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -33,6 +33,7 @@ import ChatHistoryEntry import ChatMessageItemCommon import TelegramStringFormatting import AnimatedCountLabelNode +import AudioWaveform private struct FetchControls { let fetch: (Bool) -> Void diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index d2a088b35d..783004044f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -238,7 +238,7 @@ public extension ChatReplyThreadMessage { } } -public func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool { +public func messageIsEligibleForLargeEmoji(_ message: Message) -> Bool { if !message.text.isEmpty && message.text.containsOnlyEmoji { if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) { return false @@ -249,7 +249,7 @@ public func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool { } } -public func messageIsElligibleForLargeCustomEmoji(_ message: Message) -> Bool { +public func messageIsEligibleForLargeCustomEmoji(_ message: Message) -> Bool { let text = message.text.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "") guard !text.isEmpty && text.containsOnlyEmoji else { return false diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 8f7fa30965..6920acd651 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -218,7 +218,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } } - if let item = strongSelf.item, item.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(item.message) { + if let item = strongSelf.item, item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(item.message) { if strongSelf.imageNode.frame.contains(point) { return .waitForDoubleTap } @@ -436,7 +436,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { var textLayoutAndApply: (TextNodeLayout, () -> TextNode)? var isEmoji = false - if item.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(item.message) { + if item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(item.message) { let attributedText = NSAttributedString(string: item.message.text, font: item.presentationData.messageEmojiFont, textColor: .black) textLayoutAndApply = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width, height: 90.0), alignment: .natural)) @@ -1621,7 +1621,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) var isEmoji = false - if let item = self.item, item.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(item.message) { + if let item = self.item, item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(item.message) { isEmoji = true } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index fd9a24c52d..d6b5cf4088 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -1311,6 +1311,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if item.message.containsSecretMedia { enableQuote = false } + if item.associatedData.translateToLanguage != nil { + enableQuote = false + } textSelectionNode.enableQuote = enableQuote textSelectionNode.enableTranslate = enableOtherActions diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 99026d071e..85eeb685db 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -458,6 +458,8 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent actionTitle = item.presentationData.strings.Chat_OpenStory case "telegram_channel_boost": actionTitle = item.presentationData.strings.Conversation_BoostChannel + case "telegram_group_boost": + actionTitle = item.presentationData.strings.Conversation_BoostChannel default: break } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 82039d606a..5ddd2090b0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -168,6 +168,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, hideTranslationPanel: { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { + }, openBoostToUnrestrict: { }, updateHistoryFilter: { _ in }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index b6f948777f..95782f22eb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -571,6 +571,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, displayGiveawayParticipationStatus: { _ in }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in + }, openGroupBoostInfo: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index d37a02c53d..300c165ebc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -2116,6 +2116,35 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) } + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .changeEmojiPack(_, new): + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + var text: String = "" + var entities: [MessageTextEntity] = [] + + if new != nil { + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageChangedGroupStickerPack(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + } else { + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageRemovedGroupStickerPack(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + } + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 60c3ec4039..52108d79d1 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -231,6 +231,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let displayGiveawayParticipationStatus: (EngineMessage.Id) -> Void public let openPremiumStatusInfo: (EnginePeer.Id, UIView, Int64?, PeerNameColor) -> Void public let openRecommendedChannelContextMenu: (EnginePeer, UIView, ContextGesture?) -> Void + public let openGroupBoostInfo: (EnginePeer.Id?, Int) -> Void public let requestMessageUpdate: (MessageId, Bool) -> Void public let cancelInteractiveKeyboardGestures: () -> Void @@ -353,6 +354,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol displayGiveawayParticipationStatus: @escaping (EngineMessage.Id) -> Void, openPremiumStatusInfo: @escaping (EnginePeer.Id, UIView, Int64?, PeerNameColor) -> Void, openRecommendedChannelContextMenu: @escaping (EnginePeer, UIView, ContextGesture?) -> Void, + openGroupBoostInfo: @escaping (EnginePeer.Id?, Int) -> Void, requestMessageUpdate: @escaping (MessageId, Bool) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, dismissTextInput: @escaping () -> Void, @@ -455,6 +457,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.displayGiveawayParticipationStatus = displayGiveawayParticipationStatus self.openPremiumStatusInfo = openPremiumStatusInfo self.openRecommendedChannelContextMenu = openRecommendedChannelContextMenu + self.openGroupBoostInfo = openGroupBoostInfo + self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures self.dismissTextInput = dismissTextInput diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 8cc36dd5aa..e0a4acfe4a 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -639,7 +639,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } } - if file.isPremiumEmoji && !hasPremium { + if file.isPremiumEmoji && !hasPremium && groupId != AnyHashable("peerSpecific") { var animateInAsReplacement = false if let currentUndoOverlayController = strongSelf.currentUndoOverlayController { currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation() @@ -959,6 +959,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1007,6 +1008,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { groupId: AnyHashable(info.id), title: info.title, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1065,6 +1067,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1095,6 +1098,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1365,6 +1369,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -1395,6 +1400,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index f2057cc3e8..bce0b9310a 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -578,6 +578,7 @@ public final class EmojiStatusSelectionController: ViewController { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -632,6 +633,7 @@ public final class EmojiStatusSelectionController: ViewController { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -663,6 +665,7 @@ public final class EmojiStatusSelectionController: ViewController { groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 257035d561..13dc762e1f 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -771,6 +771,8 @@ private final class GroupHeaderLayer: UIView { private var tintSubtitleLayer: SimpleLayer? private var lockIconLayer: SimpleLayer? private var tintLockIconLayer: SimpleLayer? + private var badgeLayer: SimpleLayer? + private var tintBadgeLayer: SimpleLayer? private(set) var clearIconLayer: SimpleLayer? private var tintClearIconLayer: SimpleLayer? private var separatorLayer: SimpleLayer? @@ -816,6 +818,7 @@ private final class GroupHeaderLayer: UIView { actionButtonTitle: String?, title: String, subtitle: String?, + badge: String?, isPremiumLocked: Bool, hasClear: Bool, embeddedItems: [EmojiPagerContentComponent.Item]?, @@ -831,7 +834,7 @@ private final class GroupHeaderLayer: UIView { self.theme = theme themeUpdated = true } - + let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy let textOffsetY: CGFloat @@ -978,10 +981,72 @@ private final class GroupHeaderLayer: UIView { self.tintTextLayer.isHidden = !needsVibrancy self.currentTextLayout = (title, color, textConstrainedWidth, textSize) } + + var badgeSize: CGSize = .zero + if let badge { + func generateBadgeImage(color: UIColor) -> UIImage? { + let string = NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: 120, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + let badgeSize = CGSize(width: stringBounds.width + 8.0, height: 16.0) + return generateImage(badgeSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(color.cgColor) + context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: badgeSize), cornerRadius: badgeSize.height / 2.0).cgPath) + context.fillPath() + + context.setBlendMode(.clear) + + UIGraphicsPushContext(context) + + string.draw(with: CGRect(origin: CGPoint(x: floorToScreenPixels((badgeSize.width - stringBounds.size.width) / 2.0), y: floorToScreenPixels((badgeSize.height - stringBounds.size.height) / 2.0)), size: stringBounds.size), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + UIGraphicsPopContext() + }) + } + + let badgeLayer: SimpleLayer + if let current = self.badgeLayer { + badgeLayer = current + } else { + badgeLayer = SimpleLayer() + self.badgeLayer = badgeLayer + self.layer.addSublayer(badgeLayer) + + if let image = generateBadgeImage(color: color.withMultipliedAlpha(0.66)) { + badgeLayer.contents = image.cgImage + badgeLayer.bounds = CGRect(origin: .zero, size: image.size) + } + } + badgeSize = badgeLayer.bounds.size + + let tintBadgeLayer: SimpleLayer + if let current = self.tintBadgeLayer { + tintBadgeLayer = current + } else { + tintBadgeLayer = SimpleLayer() + self.tintBadgeLayer = tintBadgeLayer + self.tintContentLayer.addSublayer(tintBadgeLayer) + + if let image = generateBadgeImage(color: .white) { + tintBadgeLayer.contents = image.cgImage + } + } + } else { + if let badgeLayer = self.badgeLayer { + self.badgeLayer = nil + badgeLayer.removeFromSuperlayer() + } + if let tintBadgeLayer = self.tintBadgeLayer { + self.tintBadgeLayer = nil + tintBadgeLayer.removeFromSuperlayer() + } + } let textFrame: CGRect if subtitle == nil { - textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset + floor((constrainedSize.width - titleHorizontalOffset - textSize.width) / 2.0), y: textOffsetY), size: textSize) + textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset + floor((constrainedSize.width - titleHorizontalOffset - (textSize.width + badgeSize.width)) / 2.0), y: textOffsetY), size: textSize) } else { textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset, y: textOffsetY), size: textSize) } @@ -989,6 +1054,11 @@ private final class GroupHeaderLayer: UIView { self.tintTextLayer.frame = textFrame self.tintTextLayer.isHidden = !needsTintText + if let badgeLayer = self.badgeLayer, let tintBadgeLayer = self.tintBadgeLayer { + badgeLayer.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 4.0, y: 0.0), size: badgeLayer.frame.size) + tintBadgeLayer.frame = badgeLayer.frame + } + if isPremiumLocked { let lockIconLayer: SimpleLayer if let current = self.lockIconLayer { @@ -2536,6 +2606,7 @@ public final class EmojiPagerContentComponent: Component { public let groupId: AnyHashable public let title: String? public let subtitle: String? + public let badge: String? public let actionButtonTitle: String? public let isFeatured: Bool public let isPremiumLocked: Bool @@ -2553,6 +2624,7 @@ public final class EmojiPagerContentComponent: Component { groupId: AnyHashable, title: String?, subtitle: String?, + badge: String?, actionButtonTitle: String?, isFeatured: Bool, isPremiumLocked: Bool, @@ -2569,6 +2641,7 @@ public final class EmojiPagerContentComponent: Component { self.groupId = groupId self.title = title self.subtitle = subtitle + self.badge = badge self.actionButtonTitle = actionButtonTitle self.isFeatured = isFeatured self.isPremiumLocked = isPremiumLocked @@ -2598,6 +2671,9 @@ public final class EmojiPagerContentComponent: Component { if lhs.subtitle != rhs.subtitle { return false } + if lhs.badge != rhs.badge { + return false + } if lhs.actionButtonTitle != rhs.actionButtonTitle { return false } @@ -5540,6 +5616,7 @@ public final class EmojiPagerContentComponent: Component { actionButtonTitle: actionButtonTitle, title: title, subtitle: itemGroup.subtitle, + badge: itemGroup.badge, isPremiumLocked: itemGroup.isPremiumLocked, hasClear: itemGroup.hasClear, embeddedItems: itemGroup.isEmbedded ? itemGroup.items : nil, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 5139ba06d4..8a266fced1 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -67,6 +67,48 @@ public extension EmojiPagerContentComponent { let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings + struct PeerSpecificPackData: Equatable { + var info: StickerPackCollectionInfo + var items: [StickerPackItem] + var peer: EnginePeer + + static func ==(lhs: PeerSpecificPackData, rhs: PeerSpecificPackData) -> Bool { + if lhs.info.id != rhs.info.id { + return false + } + if lhs.items != rhs.items { + return false + } + if lhs.peer != rhs.peer { + return false + } + + return true + } + } + + let peerSpecificPack: Signal + if let chatPeerId = chatPeerId { + peerSpecificPack = combineLatest( + context.engine.peers.peerSpecificEmojiPack(peerId: chatPeerId), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: chatPeerId)) + ) + |> map { packData, peer -> PeerSpecificPackData? in + guard let peer = peer else { + return nil + } + + guard let (info, items) = packData.packInfo else { + return nil + } + + return PeerSpecificPackData(info: info, items: items.compactMap { $0 as? StickerPackItem }, peer: peer) + } + |> distinctUntilChanged + } else { + peerSpecificPack = .single(nil) + } + var orderedItemListCollectionIds: [Int32] = [] switch subject { @@ -157,14 +199,16 @@ public extension EmojiPagerContentComponent { availableReactions, searchCategories, iconStatusEmoji, + peerSpecificPack, ApplicationSpecificNotice.dismissedTrendingEmojiPacks(accountManager: context.sharedContext.accountManager) ) - |> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji, dismissedTrendingEmojiPacks -> EmojiPagerContentComponent in + |> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji, peerSpecificPack, dismissedTrendingEmojiPacks -> EmojiPagerContentComponent in struct ItemGroup { var supergroupId: AnyHashable var id: AnyHashable var title: String? var subtitle: String? + var badge: String? var isPremiumLocked: Bool var isFeatured: Bool var collapsedLineCount: Int? @@ -198,7 +242,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) } } } @@ -274,7 +318,7 @@ public extension EmojiPagerContentComponent { itemGroupIndexById[groupId] = itemGroups.count let title = context.sharedContext.currentPresentationData.with({ $0 }).strings.EmojiInput_TrendingEmoji - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 0, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 0, isClearable: false, headerItem: nil, items: [resultItem])) } } } @@ -330,7 +374,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) } var existingIds = Set() @@ -389,7 +433,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) } var existingIds = Set() @@ -549,7 +593,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) } var existingIds = Set() @@ -695,7 +739,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) } } } @@ -773,7 +817,7 @@ public extension EmojiPagerContentComponent { } } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) } } @@ -831,7 +875,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: hasRecent && subject != .quickReaction, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: hasRecent && subject != .quickReaction, headerItem: nil, items: [resultItem])) } } else { let groupId = "recent" @@ -843,7 +887,7 @@ public extension EmojiPagerContentComponent { } } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) } } } @@ -915,7 +959,7 @@ public extension EmojiPagerContentComponent { popularInsertIndex += 1 } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: hasRecent && subject != .quickReaction, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: hasRecent && subject != .quickReaction, headerItem: nil, items: [resultItem])) } } } @@ -980,7 +1024,7 @@ public extension EmojiPagerContentComponent { } } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) } } } else if [.profilePhoto, .groupPhoto].contains(subject) { @@ -988,7 +1032,7 @@ public extension EmojiPagerContentComponent { let groupId = "recent" itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [])) if let featuredAvatarEmoji = featuredAvatarEmoji { for item in featuredAvatarEmoji.items { @@ -1057,7 +1101,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) } if let featuredBackgroundIconEmoji { @@ -1165,23 +1209,66 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Emoji_FrequentlyUsed, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: true, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Emoji_FrequentlyUsed, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: true, headerItem: nil, items: [resultItem])) } } } - - if !hasPremium { - maybeAppendUnicodeEmoji() - } - + var itemCollectionMapping: [ItemCollectionId: StickerPackCollectionInfo] = [:] for (id, info, _) in view.collectionInfos { if let info = info as? StickerPackCollectionInfo { itemCollectionMapping[id] = info } } - + var skippedCollectionIds = Set() + + var avatarPeer: EnginePeer? + if let peerSpecificPack = peerSpecificPack { + avatarPeer = peerSpecificPack.peer + + var processedIds = Set() + for item in peerSpecificPack.items { + if isPremiumDisabled && item.file.isPremiumSticker { + continue + } + if processedIds.contains(item.file.fileId) { + continue + } + processedIds.insert(item.file.fileId) + + var tintMode: Item.TintMode = .none + if item.file.isCustomTemplateEmoji { + tintMode = .primary + } + + let animationData = EntityKeyboardAnimationData(file: item.file) + let resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: item.file, + subgroupId: nil, + icon: .none, + tintMode: tintMode + ) + + let groupId = "peerSpecific" + if let groupIndex = itemGroupIndexById[groupId] { + itemGroups[groupIndex].items.append(resultItem) + } else { + itemGroupIndexById[groupId] = itemGroups.count + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: peerSpecificPack.info.title, subtitle: nil, badge: strings.Emoji_GroupEmoji, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + } + } + + let supergroupId: AnyHashable = peerSpecificPack.info.id + skippedCollectionIds.insert(supergroupId) + } + + if !hasPremium { + maybeAppendUnicodeEmoji() + } + if areCustomEmojiEnabled { for entry in view.entries { guard let item = entry.item as? StickerPackItem else { @@ -1278,7 +1365,7 @@ public extension EmojiPagerContentComponent { break inner } } - itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: headerItem, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, subtitle: nil, badge: nil, isPremiumLocked: isPremiumLocked, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: headerItem, items: [resultItem])) } } @@ -1367,7 +1454,7 @@ public extension EmojiPagerContentComponent { isFeatured = false } - itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: isFeatured, collapsedLineCount: 3, isClearable: false, headerItem: headerItem, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, badge: nil, isPremiumLocked: isPremiumLocked, isFeatured: isFeatured, collapsedLineCount: 3, isClearable: false, headerItem: headerItem, items: [resultItem])) } } } @@ -1422,6 +1509,7 @@ public extension EmojiPagerContentComponent { groupId: group.id, title: group.title, subtitle: group.subtitle, + badge: group.badge, actionButtonTitle: nil, isFeatured: group.isFeatured, isPremiumLocked: group.isPremiumLocked, @@ -1442,7 +1530,7 @@ public extension EmojiPagerContentComponent { return EmojiPagerContentComponent( id: "emoji", context: context, - avatarPeer: nil, + avatarPeer: avatarPeer, animationCache: animationCache, animationRenderer: animationRenderer, inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(), @@ -1908,6 +1996,7 @@ public extension EmojiPagerContentComponent { groupId: group.id, title: group.title, subtitle: group.subtitle, + badge: nil, actionButtonTitle: group.actionButtonTitle, isFeatured: group.isFeatured, isPremiumLocked: group.isPremiumLocked, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index cc21a8caa1..6307c7d3d0 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -122,6 +122,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode groupId: AnyHashable(groupItem.info.id), title: groupItem.info.title, subtitle: nil, + badge: nil, actionButtonTitle: self.presentationData.strings.EmojiInput_AddPack(groupItem.info.title).string, isFeatured: true, isPremiumLocked: !self.hasPremiumForInstallation, @@ -286,6 +287,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -340,6 +342,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, @@ -373,6 +376,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode groupId: "search", title: nil, subtitle: nil, + badge: nil, actionButtonTitle: nil, isFeatured: false, isPremiumLocked: false, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index fd938c10b1..3abfa3e7f2 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -571,7 +571,7 @@ public final class EntityKeyboardComponent: Component { var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = [] for itemGroup in emojiContent.panelItemGroups { if !itemGroup.items.isEmpty { - if let id = itemGroup.groupId.base as? String { + if let id = itemGroup.groupId.base as? String, id != "peerSpecific" { if id == "recent" || id == "liked" { let iconMapping: [String: EntityKeyboardIconTopPanelComponent.Icon] = [ "recent": .recent, diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD new file mode 100644 index 0000000000..8122488eeb --- /dev/null +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD @@ -0,0 +1,30 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GroupStickerPackSetupController", + module_name = "GroupStickerPackSetupController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/ItemListUI:ItemListUI", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/AccountContext:AccountContext", + "//submodules/StickerPackPreviewUI:StickerPackPreviewUI", + "//submodules/ItemListStickerPackItem:ItemListStickerPackItem", + "//submodules/SearchBarNode:SearchBarNode", + "//submodules/SearchUI:SearchUI", + "//submodules/MergeLists:MergeLists", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Info.plist b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Info.plist new file mode 100644 index 0000000000..e1fe4cfb7b --- /dev/null +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/submodules/PeerInfoUI/Sources/GroupStickerPackCurrentItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift similarity index 91% rename from submodules/PeerInfoUI/Sources/GroupStickerPackCurrentItem.swift rename to submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift index 2f32d43117..c87e720881 100644 --- a/submodules/PeerInfoUI/Sources/GroupStickerPackCurrentItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift @@ -89,6 +89,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode fileprivate let imageNode: TransformImageNode private let notFoundNode: ASImageNode @@ -121,6 +122,9 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode.isLayerBacked = true + self.maskNode = ASImageNode() + self.maskNode.isUserInteractionEnabled = false + self.imageNode = TransformImageNode() self.imageNode.isLayerBacked = !smartInvertColorsEnabled() @@ -282,11 +286,35 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { if strongSelf.bottomStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) } + if strongSelf.maskNode.supernode == nil { + strongSelf.addSubnode(strongSelf.maskNode) + } +// switch neighbors.top { +// case .sameSection(false): +// strongSelf.topStripeNode.isHidden = true +// default: +// strongSelf.topStripeNode.isHidden = false +// } +// let bottomStripeInset: CGFloat +// let bottomStripeOffset: CGFloat +// switch neighbors.bottom { +// case .sameSection(false): +// bottomStripeInset = leftInset + editingOffset +// bottomStripeOffset = -separatorHeight +// default: +// bottomStripeInset = 0.0 +// bottomStripeOffset = 0.0 +// } + + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false switch neighbors.top { case .sameSection(false): strongSelf.topStripeNode.isHidden = true default: - strongSelf.topStripeNode.isHidden = false + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat @@ -294,11 +322,18 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight + strongSelf.bottomStripeNode.isHidden = false default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.backgroundNode.frame = 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.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) diff --git a/submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift similarity index 78% rename from submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift rename to submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift index 1c3e5ae2c2..e730dba087 100644 --- a/submodules/PeerInfoUI/Sources/GroupStickerPackSetupController.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift @@ -202,11 +202,12 @@ private enum GroupStickerPackEntry: ItemListNodeEntry { let arguments = arguments as! GroupStickerPackSetupControllerArguments switch self { case let .search(theme, _, prefix, placeholder, value): + let isEmoji = prefix.contains("addemoji") return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), spacing: 0.0, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { value in arguments.updateSearchText(value) }, processPaste: { text in if let url = (URL(string: text) ?? URL(string: "http://" + text)), url.host == "t.me" || url.host == "telegram.me" { - let prefix = "/addstickers/" + let prefix = isEmoji ? "/addemoji/" : "/addstickers/" if url.path.hasPrefix(prefix) { return String(url.path[url.path.index(url.path.startIndex, offsetBy: prefix.count)...]) } @@ -258,15 +259,16 @@ private enum GroupStickerPackSearchState: Equatable { private struct GroupStickerPackSetupControllerState: Equatable { var isSaving: Bool + var searchingPacks: Bool } -private func groupStickerPackSetupControllerEntries(context: AccountContext, presentationData: PresentationData, searchText: String, view: CombinedView, initialData: InitialStickerPackData?, searchState: GroupStickerPackSearchState, stickerSettings: StickerSettings) -> [GroupStickerPackEntry] { +private func groupStickerPackSetupControllerEntries(context: AccountContext, presentationData: PresentationData, searchText: String, view: CombinedView, initialData: InitialStickerPackData?, searchState: GroupStickerPackSearchState, stickerSettings: StickerSettings, emoji: Bool) -> [GroupStickerPackEntry] { if initialData == nil { return [] } var entries: [GroupStickerPackEntry] = [] - entries.append(.search(presentationData.theme, presentationData.strings, "t.me/addstickers/", presentationData.strings.Channel_Stickers_Placeholder, searchText)) + entries.append(.search(presentationData.theme, presentationData.strings, emoji ? "t.me/addemoji/" : "t.me/addstickers/", emoji ? "emojiset" : presentationData.strings.Channel_Stickers_Placeholder, searchText)) switch searchState { case .none: break @@ -275,12 +277,12 @@ private func groupStickerPackSetupControllerEntries(context: AccountContext, pre case .searching: entries.append(.currentPack(0, presentationData.theme, presentationData.strings, .searching)) case let .found(data): - entries.append(.currentPack(0, presentationData.theme, presentationData.strings, .found(packInfo: data.info, topItem: data.item, subtitle: presentationData.strings.StickerPack_StickerCount(data.info.count)))) + entries.append(.currentPack(0, presentationData.theme, presentationData.strings, .found(packInfo: data.info, topItem: data.item, subtitle: emoji ? presentationData.strings.StickerPack_EmojiCount(data.info.count) : presentationData.strings.StickerPack_StickerCount(data.info.count)))) } - entries.append(.searchInfo(presentationData.theme, presentationData.strings.Channel_Stickers_CreateYourOwn)) - entries.append(.packsTitle(presentationData.theme, presentationData.strings.Channel_Stickers_YourStickers)) + entries.append(.searchInfo(presentationData.theme, emoji ? "All members will be able to use these emoji in the group, even if they don't have Telegram Premium." : presentationData.strings.Channel_Stickers_CreateYourOwn)) + entries.append(.packsTitle(presentationData.theme, emoji ? "CHOOSE EMOJI PACK" : presentationData.strings.Channel_Stickers_YourStickers)) - let namespace = Namespaces.ItemCollection.CloudStickerPacks + let namespace = emoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespace])] as? ItemCollectionInfosView { if let packsEntries = stickerPacksView.entriesByNamespace[namespace] { var index: Int32 = 0 @@ -290,7 +292,15 @@ private func groupStickerPackSetupControllerEntries(context: AccountContext, pre if case let .found(found) = searchState { selected = found.info.id == info.id } - entries.append(.pack(index, presentationData.theme, presentationData.strings, info, entry.firstItem as? StickerPackItem, presentationData.strings.StickerPack_StickerCount(info.count == 0 ? entry.count : info.count), context.sharedContext.energyUsageSettings.loopStickers, selected)) + let count = info.count == 0 ? entry.count : info.count + + let thumbnail: StickerPackItem? + if let thumbnailRep = info.thumbnail { + thumbnail = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: thumbnailRep.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: info.immediateThumbnailData, mimeType: "", size: nil, attributes: []), indexKeys: []) + } else { + thumbnail = entry.firstItem as? StickerPackItem + } + entries.append(.pack(index, presentationData.theme, presentationData.strings, info, thumbnail, emoji ? presentationData.strings.StickerPack_EmojiCount(count) : presentationData.strings.StickerPack_StickerCount(count), context.sharedContext.energyUsageSettings.loopStickers, selected)) index += 1 } } @@ -300,8 +310,8 @@ private func groupStickerPackSetupControllerEntries(context: AccountContext, pre return entries } -public func groupStickerPackSetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, currentPackInfo: StickerPackCollectionInfo?) -> ViewController { - let initialState = GroupStickerPackSetupControllerState(isSaving: false) +public func groupStickerPackSetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, emoji: Bool = false, currentPackInfo: StickerPackCollectionInfo?, completion: ((StickerPackCollectionInfo?) -> Void)? = nil) -> ViewController { + let initialState = GroupStickerPackSetupControllerState(isSaving: false, searchingPacks: false) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -329,7 +339,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres } let stickerPacks = Promise() - stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [emoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks])])) let searchState = Promise<(String, GroupStickerPackSearchState)>() searchState.set(combineLatest(searchText.get(), initialData.get(), stickerPacks.get()) @@ -340,7 +350,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres } else if case let .data(data) = initialData, searchText.lowercased() == data.info.shortName { return .single((searchText, .found(StickerPackData(info: data.info, item: data.item)))) } else { - let namespace = Namespaces.ItemCollection.CloudStickerPacks + let namespace = emoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespace])] as? ItemCollectionInfosView { if let packsEntries = stickerPacksView.entriesByNamespace[namespace] { for entry in packsEntries { @@ -387,10 +397,16 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres let arguments = GroupStickerPackSetupControllerArguments(context: context, selectStickerPack: { info in searchText.set(info.shortName) + if let completion { + completion(info) + } }, openStickerPack: { info in presentStickerPackController?(info) }, updateSearchText: { text in searchText.set(text) + if text == "", let completion { + completion(nil) + } }, openStickersBot: { resolveDisposable.set((context.engine.peers.resolvePeerByName(name: "stickers") |> mapToSignal { result -> Signal in @@ -417,18 +433,32 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres stickerSettings = value } - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { - dismissImpl?() - }) + let leftNavigationButton: ItemListNavigationButton? + if emoji { + leftNavigationButton = nil + } else { + leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + dismissImpl?() + }) + } var rightNavigationButton: ItemListNavigationButton? - if initialData != nil { - if state.isSaving { - rightNavigationButton = ItemListNavigationButton(content: .text(""), style: .activity, enabled: true, action: {}) - } else { - let enabled: Bool - var info: StickerPackCollectionInfo? - switch searchState.1 { + if let _ = completion { + rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: { + updateState { state in + var updatedState = state + updatedState.searchingPacks = true + return updatedState + } + }) + } else { + if initialData != nil { + if state.isSaving { + rightNavigationButton = ItemListNavigationButton(content: .text(""), style: .activity, enabled: true, action: {}) + } else { + let enabled: Bool + var info: StickerPackCollectionInfo? + switch searchState.1 { case .searching, .notFound: enabled = false case .none: @@ -436,32 +466,53 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres case let .found(data): enabled = true info = data.info - } - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: { - if info?.id == currentPackInfo?.id { - dismissImpl?() - } else { - updateState { state in - var state = state - state.isSaving = true - return state - } - saveDisposable.set((context.engine.peers.updateGroupSpecificStickerset(peerId: peerId, info: info) - |> deliverOnMainQueue).start(error: { _ in - updateState { state in - var state = state - state.isSaving = false - return state - } - }, completed: { - dismissImpl?() - })) } - }) + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: { + if let completion { + completion(info) + dismissImpl?() + } else { + if info?.id == currentPackInfo?.id { + dismissImpl?() + } else { + updateState { state in + var state = state + state.isSaving = true + return state + } + saveDisposable.set((context.engine.peers.updateGroupSpecificStickerset(peerId: peerId, info: info) + |> deliverOnMainQueue).start(error: { _ in + updateState { state in + var state = state + state.isSaving = false + return state + } + }, completed: { + dismissImpl?() + })) + } + } + }) + } } } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Channel_Info_Stickers), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) + var searchItem: ItemListControllerSearch? + if state.searchingPacks { + searchItem = GroupStickerSearchItem(context: context, cancel: { + updateState { state in + var updatedState = state + updatedState.searchingPacks = false + return updatedState + } + }, select: { pack in + arguments.selectStickerPack(pack) + }, dismissInput: { + dismissInputImpl?() + }) + } + + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(emoji ? "Group Emoji Pack" : presentationData.strings.Channel_Info_Stickers), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let hasData = initialData != nil let hadData = previousHadData.swap(hasData) @@ -471,7 +522,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStickerPackSetupControllerEntries(context: context, presentationData: presentationData, searchText: searchState.0, view: view, initialData: initialData, searchState: searchState.1, stickerSettings: stickerSettings), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: hasData && hadData) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStickerPackSetupControllerEntries(context: context, presentationData: presentationData, searchText: searchState.0, view: view, initialData: initialData, searchState: searchState.1, stickerSettings: stickerSettings, emoji: emoji), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: hasData && hadData) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchContainerNode.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchContainerNode.swift new file mode 100644 index 0000000000..89ea9480b3 --- /dev/null +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchContainerNode.swift @@ -0,0 +1,328 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import MergeLists +import AccountContext +import SearchUI +import ItemListUI +import ItemListStickerPackItem + +private final class GroupStickerSearchContainerInteraction { + let packSelected: (StickerPackCollectionInfo) -> Void + + init(packSelected: @escaping (StickerPackCollectionInfo) -> Void) { + self.packSelected = packSelected + } +} + +private final class GroupStickerSearchEntry: Comparable, Identifiable { + let index: Int + let pack: StickerPackCollectionInfo + let topItem: ItemCollectionItem? + + init(index: Int, pack: StickerPackCollectionInfo, topItem: ItemCollectionItem?) { + self.index = index + self.pack = pack + self.topItem = topItem + } + + var stableId: ItemCollectionId { + return self.pack.id + } + + static func ==(lhs: GroupStickerSearchEntry, rhs: GroupStickerSearchEntry) -> Bool { + return lhs.index == rhs.index && lhs.pack == rhs.pack + } + + static func <(lhs: GroupStickerSearchEntry, rhs: GroupStickerSearchEntry) -> Bool { + return lhs.index < rhs.index + } + + func item(context: AccountContext, presentationData: PresentationData, interaction: GroupStickerSearchContainerInteraction) -> ListViewItem { + let pack = self.pack + let count = presentationData.strings.StickerPack_EmojiCount(pack.count) + + return ItemListStickerPackItem(presentationData: ItemListPresentationData(presentationData), context: context, packInfo: pack, itemCount: count, topItem: self.topItem as? StickerPackItem, unread: false, control: .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: true, style: .plain, sectionId: 0, action: { + interaction.packSelected(pack) + }, setPackIdWithRevealedOptions: { _, _ in + }, addPack: { + }, removePack: { + }, toggleSelected: { + }) + } +} + +struct GroupStickerSearchContainerTransition { + let deletions: [ListViewDeleteItem] + let insertions: [ListViewInsertItem] + let updates: [ListViewUpdateItem] + let isSearching: Bool + let isEmpty: Bool + let query: String +} + +private func groupStickerSearchContainerPreparedRecentTransition(from fromEntries: [GroupStickerSearchEntry], to toEntries: [GroupStickerSearchEntry], isSearching: Bool, isEmpty: Bool, query: String, context: AccountContext, presentationData: PresentationData, interaction: GroupStickerSearchContainerInteraction) -> GroupStickerSearchContainerTransition { + 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, presentationData: presentationData, interaction: interaction), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) } + + return GroupStickerSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, isEmpty: isEmpty, query: query) +} + +public final class GroupStickerSearchContainerNode: SearchDisplayControllerContentNode { + private let context: AccountContext + private let packSelected: (StickerPackCollectionInfo) -> Void + + private let listNode: ListView + + private let emptyResultsTitleNode: ImmediateTextNode + private let emptyResultsTextNode: ImmediateTextNode + + private var enqueuedTransitions: [(GroupStickerSearchContainerTransition, Bool)] = [] + private var validLayout: (ContainerViewLayout, CGFloat)? + + private let searchQuery = Promise() + private let searchDisposable = MetaDisposable() + + private let forceTheme: PresentationTheme? + private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? + + private let removeMemberDisposable = MetaDisposable() + + private let presentationDataPromise: Promise + + private var _hasDim: Bool = false + override public var hasDim: Bool { + return _hasDim + } + + public init(context: AccountContext, forceTheme: PresentationTheme?, packSelected: @escaping (StickerPackCollectionInfo) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) { + self.context = context + self.packSelected = packSelected + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationData = presentationData + + self.forceTheme = forceTheme + if let forceTheme = self.forceTheme { + self.presentationData = self.presentationData.withUpdated(theme: forceTheme) + } + self.presentationDataPromise = Promise(self.presentationData) + + self.listNode = ListView() + self.listNode.accessibilityPageScrolledString = { row, count in + return presentationData.strings.VoiceOver_ScrollStatus(row, count).string + } + + self.emptyResultsTitleNode = ImmediateTextNode() + self.emptyResultsTitleNode.displaysAsynchronously = false + self.emptyResultsTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.ChatList_Search_NoResults, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.freeTextColor) + self.emptyResultsTitleNode.textAlignment = .center + self.emptyResultsTitleNode.isHidden = true + + self.emptyResultsTextNode = ImmediateTextNode() + self.emptyResultsTextNode.displaysAsynchronously = false + self.emptyResultsTextNode.maximumNumberOfLines = 0 + self.emptyResultsTextNode.textAlignment = .center + self.emptyResultsTextNode.isHidden = true + + super.init() + + self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.listNode.isHidden = true + + self._hasDim = true + + self.addSubnode(self.listNode) + + self.addSubnode(self.emptyResultsTitleNode) + self.addSubnode(self.emptyResultsTextNode) + + let interaction = GroupStickerSearchContainerInteraction(packSelected: { [weak self] pack in + packSelected(pack) + self?.listNode.clearHighlightAnimated(true) + }) + + let foundItems = self.searchQuery.get() + |> mapToSignal { query -> Signal<[GroupStickerSearchEntry]?, NoError> in + guard let query, !query.isEmpty else { + return .single(nil) + } + return context.engine.stickers.searchEmojiSets(query: query) + |> mapToSignal { localResult in + return context.engine.stickers.searchEmojiSetsRemotely(query: query) + |> map { remoteResult -> [GroupStickerSearchEntry]? in + let mergedResult = localResult.merge(with: remoteResult) + var entries: [GroupStickerSearchEntry] = [] + var index = 0 + for info in mergedResult.infos { + if let pack = info.1 as? StickerPackCollectionInfo { + entries.append(GroupStickerSearchEntry(index: index, pack: pack, topItem: info.2)) + index += 1 + } + } + return entries + } + } + } + + let previousSearchItems = Atomic<[GroupStickerSearchEntry]?>(value: nil) + self.searchDisposable.set((combineLatest(self.searchQuery.get(), foundItems, self.presentationDataPromise.get()) + |> deliverOnMainQueue).start(next: { [weak self] query, entries, presentationData in + if let strongSelf = self { + let previousEntries = previousSearchItems.swap(entries) + updateActivity(false) + let firstTime = previousEntries == nil + let transition = groupStickerSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, isEmpty: entries?.isEmpty ?? false, query: query ?? "", context: context, presentationData: presentationData, interaction: interaction) + strongSelf.enqueueTransition(transition, firstTime: firstTime) + } + })) + + self.presentationDataDisposable = (context.sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + var presentationData = presentationData + + let previousTheme = strongSelf.presentationData.theme + let previousStrings = strongSelf.presentationData.strings + + if let forceTheme = strongSelf.forceTheme { + presentationData = presentationData.withUpdated(theme: forceTheme) + } + + strongSelf.presentationData = presentationData + + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { + strongSelf.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) + } + } + }) + + self.listNode.beganInteractiveDragging = { [weak self] _ in + self?.dismissInput?() + } + } + + deinit { + self.searchDisposable.dispose() + self.presentationDataDisposable?.dispose() + } + + private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.listNode.backgroundColor = theme.chatList.backgroundColor + } + + override public func searchTextUpdated(text: String) { + if text.isEmpty { + self.searchQuery.set(.single(nil)) + } else { + self.searchQuery.set(.single(text)) + } + } + + private func enqueueTransition(_ transition: GroupStickerSearchContainerTransition, firstTime: Bool) { + enqueuedTransitions.append((transition, firstTime)) + + if let _ = self.validLayout { + while !self.enqueuedTransitions.isEmpty { + self.dequeueTransition() + } + } + } + + private func dequeueTransition() { + if let (transition, firstTime) = self.enqueuedTransitions.first { + self.enqueuedTransitions.remove(at: 0) + + var options = ListViewDeleteAndInsertOptions() + options.insert(.PreferSynchronousDrawing) + options.insert(.PreferSynchronousResourceLoading) + if firstTime { + } else { + //options.insert(.AnimateAlpha) + } + + let isSearching = transition.isSearching + self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + + strongSelf.listNode.isHidden = !isSearching + + strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(transition.query).string, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) + + let emptyResults = transition.isSearching && transition.isEmpty + strongSelf.emptyResultsTitleNode.isHidden = !emptyResults + strongSelf.emptyResultsTextNode.isHidden = !emptyResults + + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + }) + } + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) + + let hadValidLayout = self.validLayout == nil + self.validLayout = (layout, navigationBarHeight) + + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) + + var insets = layout.insets(options: [.input]) + insets.top += navigationBarHeight + insets.left += layout.safeInsets.left + insets.right += layout.safeInsets.right + + self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + + let padding: CGFloat = 16.0 + let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) + let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) + + let emptyTextSpacing: CGFloat = 8.0 + let emptyTotalHeight = emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing + let emptyTitleY = navigationBarHeight + floorToScreenPixels((layout.size.height - navigationBarHeight - max(insets.bottom, layout.intrinsicInsets.bottom) - emptyTotalHeight) / 2.0) + + transition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyTitleY), size: emptyTitleSize)) + transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyTitleY + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) + + if !hadValidLayout { + while !self.enqueuedTransitions.isEmpty { + self.dequeueTransition() + } + } + } + + override public func scrollToTop() { + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.cancel?() + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let result = self.view.hitTest(point, with: event) else { + return nil + } + if result === self.view { + return nil + } + return result + } +} diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift new file mode 100644 index 0000000000..e22fd30031 --- /dev/null +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift @@ -0,0 +1,119 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import TelegramCore +import SwiftSignalKit +import ItemListUI +import PresentationDataUtils +import AccountContext + +final class GroupStickerSearchItem: ItemListControllerSearch { + let context: AccountContext + let cancel: () -> Void + let select: (StickerPackCollectionInfo) -> Void + let dismissInput: () -> Void + + private var updateActivity: ((Bool) -> Void)? + private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) + private let activityDisposable = MetaDisposable() + + init( + context: AccountContext, + cancel: @escaping () -> Void, + select: @escaping (StickerPackCollectionInfo) -> Void, + dismissInput: @escaping () -> Void + ) { + self.context = context + self.cancel = cancel + self.select = select + self.dismissInput = dismissInput + self.activityDisposable.set((self.activity.get() |> mapToSignal { value -> Signal in + if value { + return .single(value) |> delay(0.2, queue: Queue.mainQueue()) + } else { + return .single(value) + } + }).start(next: { [weak self] value in + self?.updateActivity?(value) + })) + } + + deinit { + self.activityDisposable.dispose() + } + + func isEqual(to: ItemListControllerSearch) -> Bool { + if let to = to as? GroupStickerSearchItem { + if self.context !== to.context { + return false + } + return true + } else { + return false + } + } + + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + if let current = current as? GroupStickerSearchNavigationContentNode { + current.updateTheme(presentationData.theme) + return current + } else { + return GroupStickerSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, cancel: self.cancel, updateActivity: { [weak self] value in + self?.updateActivity = value + }) + } + } + + func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode { + return GroupStickerSearchItemNode(context: self.context, packSelected: self.select, cancel: self.cancel, updateActivity: { [weak self] value in + self?.activity.set(value) + }, pushController: { c in + + }, dismissInput: self.dismissInput) + } +} + +private final class GroupStickerSearchItemNode: ItemListControllerSearchNode { + private let containerNode: GroupStickerSearchContainerNode + + init(context: AccountContext, packSelected: @escaping (StickerPackCollectionInfo) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) { + self.containerNode = GroupStickerSearchContainerNode(context: context, forceTheme: nil, packSelected: { pack in + packSelected(pack) + cancel() + }, updateActivity: updateActivity, pushController: pushController) + self.containerNode.cancel = { + cancel() + } + + super.init() + + self.addSubnode(self.containerNode) + + self.containerNode.dismissInput = { + dismissInput() + } + } + + override func queryUpdated(_ query: String) { + self.containerNode.searchTextUpdated(text: query) + } + + override func scrollToTop() { + self.containerNode.scrollToTop() + } + + override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) + self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let result = self.containerNode.hitTest(self.view.convert(point, to: self.containerNode.view), with: event) { + return result + } + + return super.hitTest(point, with: event) + } +} diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift new file mode 100644 index 0000000000..408019f90b --- /dev/null +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift @@ -0,0 +1,88 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import ItemListUI +import PresentationDataUtils +import SearchBarNode + +private let searchBarFont = Font.regular(17.0) + +final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode { + private var theme: PresentationTheme + private let strings: PresentationStrings + + private let cancel: () -> Void + + private let searchBar: SearchBarNode + + private var queryUpdated: ((String) -> Void)? + var activity: Bool = false { + didSet { + self.searchBar.activity = activity + } + } + init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) { + self.theme = theme + self.strings = strings + + self.cancel = cancel + + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false) + + super.init() + + self.addSubnode(self.searchBar) + + self.searchBar.cancel = { [weak self] in + self?.searchBar.deactivate(clear: false) + self?.cancel() + } + + self.searchBar.textUpdated = { [weak self] query, _ in + self?.queryUpdated?(query) + } + + updateActivity({ [weak self] value in + self?.activity = value + }) + + self.updatePlaceholder() + } + + func setQueryUpdated(_ f: @escaping (String) -> Void) { + self.queryUpdated = f + } + + func updateTheme(_ theme: PresentationTheme) { + self.theme = theme + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), strings: self.strings) + self.updatePlaceholder() + } + + func updatePlaceholder() { + self.searchBar.placeholderString = NSAttributedString(string: self.strings.Common_Search, font: searchBarFont, textColor: self.theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + } + + override var nominalHeight: CGFloat { + return 54.0 + } + + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) + self.searchBar.frame = searchBarFrame + self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + } + + func activate() { + self.searchBar.activate() + } + + func deactivate() { + self.searchBar.deactivate(clear: false) + } +} + diff --git a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift index fb5467ad0a..957dca00c5 100644 --- a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift +++ b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift @@ -103,7 +103,7 @@ public final class ListActionItemComponent: Component { transition: transition, component: component.title, environment: {}, - containerSize: CGSize(width: availableSize.width - contentLeftInset, height: availableSize.height) + containerSize: CGSize(width: availableSize.width - contentLeftInset - contentRightInset, height: availableSize.height) ) let titleFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: verticalInset), size: titleSize) if let titleView = self.title.view { diff --git a/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift b/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift index 5612d0f692..afea63de5f 100644 --- a/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift +++ b/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift @@ -13,7 +13,7 @@ public final class ListSectionComponent: Component { public typealias ChildView = ListSectionComponentChildView public enum Background: Equatable { - case none + case none(clipped: Bool) case all case range(from: AnyHashable, corners: DynamicCornerRadiusView.Corners) } @@ -71,7 +71,6 @@ public final class ListSectionComponent: Component { public override init(frame: CGRect) { self.contentView = UIView() - self.contentView.layer.cornerRadius = 11.0 self.contentView.clipsToBounds = true self.contentBackgroundView = DynamicCornerRadiusView() @@ -219,11 +218,15 @@ public final class ListSectionComponent: Component { let backgroundFrame: CGRect var backgroundAlpha: CGFloat = 1.0 + var contentCornerRadius: CGFloat = 11.0 switch component.background { - case .none: + case let .none(clipped): backgroundFrame = contentFrame backgroundAlpha = 0.0 self.contentBackgroundView.update(size: backgroundFrame.size, corners: DynamicCornerRadiusView.Corners(minXMinY: 11.0, maxXMinY: 11.0, minXMaxY: 11.0, maxXMaxY: 11.0), transition: transition) + if !clipped { + contentCornerRadius = 0.0 + } case .all: backgroundFrame = contentFrame self.contentBackgroundView.update(size: backgroundFrame.size, corners: DynamicCornerRadiusView.Corners(minXMinY: 11.0, maxXMinY: 11.0, minXMaxY: 11.0, maxXMaxY: 11.0), transition: transition) @@ -237,7 +240,8 @@ public final class ListSectionComponent: Component { } transition.setFrame(view: self.contentBackgroundView, frame: backgroundFrame) transition.setAlpha(view: self.contentBackgroundView, alpha: backgroundAlpha) - + transition.setCornerRadius(layer: self.contentView.layer, cornerRadius: contentCornerRadius) + contentHeight += innerContentHeight if let footerValue = component.footer { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 368bde65c1..310be1dcc6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -104,6 +104,7 @@ import AttachmentUI import BoostLevelIconComponent import PeerInfoChatPaneNode import PeerInfoChatListPaneNode +import GroupStickerPackSetupController public enum PeerInfoAvatarEditingMode { case generic @@ -416,6 +417,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, hideTranslationPanel: { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { + }, openBoostToUnrestrict: { }, updateHistoryFilter: { _ in }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in @@ -544,7 +546,7 @@ private final class PeerInfoInteraction { let editingToggleMessageSignatures: (Bool) -> Void let openParticipantsSection: (PeerInfoParticipantsSection) -> Void let openRecentActions: () -> Void - let openStats: () -> Void + let openStats: (Bool) -> Void let editingOpenPreHistorySetup: () -> Void let editingOpenAutoremoveMesages: () -> Void let openPermissions: () -> Void @@ -599,7 +601,7 @@ private final class PeerInfoInteraction { editingToggleMessageSignatures: @escaping (Bool) -> Void, openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void, openRecentActions: @escaping () -> Void, - openStats: @escaping () -> Void, + openStats: @escaping (Bool) -> Void, editingOpenPreHistorySetup: @escaping () -> Void, editingOpenAutoremoveMesages: @escaping () -> Void, openPermissions: @escaping () -> Void, @@ -1751,7 +1753,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) { items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStats, label: .none, text: presentationData.strings.Channel_Info_Stats, icon: UIImage(bundleImageName: "Chat/Info/StatsIcon"), action: { - interaction.openStats() + interaction.openStats(false) })) } @@ -1795,6 +1797,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL let ItemReactions = 116 let ItemTopics = 117 let ItemTopicsText = 118 + let ItemAppearance = 119 let isCreator = channel.flags.contains(.isCreator) let isPublic = channel.addressName != nil @@ -1907,6 +1910,43 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL } } + if isCreator || channel.adminRights?.rights.contains(.canChangeInfo) == true { + var colors: [PeerNameColors.Colors] = [] + if let nameColor = channel.nameColor.flatMap({ context.peerNameColors.get($0, dark: presentationData.theme.overallDarkAppearance) }) { + colors.append(nameColor) + } + if let profileColor = channel.profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: presentationData.theme.overallDarkAppearance, subject: .palette) }) { + colors.append(profileColor) + } + let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) + + var boostIcon: UIImage? + if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 { + boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string) + } else { + let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) + let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) + let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height)) + let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0) + boostIcon = generateImage(badgeSize, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0)) + + context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath) + context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) + context.fillPath() + + UIGraphicsPushContext(context) + labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) + UIGraphicsPopContext() + }) + } + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAppearance, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: { + interaction.editingOpenNameColorSetup() + })) + } + if (isCreator || (channel.adminRights != nil && channel.hasPermission(.banMembers))) && cachedData.peerGeoLocation == nil, !isPublic, case .known(nil) = cachedData.linkedDiscussionPeerId, !channel.flags.contains(.isForum) { items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(cachedData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistoryShort, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { interaction.editingOpenPreHistorySetup() @@ -1995,7 +2035,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL interaction.openParticipantsSection(.memberRequests) })) } - + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: { interaction.openParticipantsSection(.banned) })) @@ -2399,8 +2439,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro openRecentActions: { [weak self] in self?.openRecentActions() }, - openStats: { [weak self] in - self?.openStats() + openStats: { [weak self] boosts in + self?.openStats(boosts: boosts) }, editingOpenPreHistorySetup: { [weak self] in self?.editingOpenPreHistorySetup() @@ -3024,6 +3064,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, displayGiveawayParticipationStatus: { _ in }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in + }, openGroupBoostInfo: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -3912,7 +3953,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var previousTimestamp: Double? self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPack, white in - guard let strongSelf = self else { + guard let strongSelf = self, let peer = strongSelf.data?.peer else { return } let currentTimestamp = CACurrentMediaTime() @@ -3932,7 +3973,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro |> take(1) |> mapToSignal { emojiStatusFileAndPack -> Signal in if let (file, pack) = emojiStatusFileAndPack { - return .single(.emojiStatus(strongSelf.peerId, peerStatus.fileId, file, pack)) + return .single(.emojiStatus(peer.id, peerStatus.fileId, file, pack)) } else { return .complete() } @@ -4100,11 +4141,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.boostStatusDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> mapToSignal { peer -> Signal in - if case let .channel(channel) = peer, (channel.flags.contains(.isCreator) || channel.adminRights != nil) { - return context.engine.peers.getChannelBoostStatus(peerId: peerId) - } else { - return .single(nil) + if case let .channel(channel) = peer { + if case .broadcast = channel.info, (channel.flags.contains(.isCreator) || channel.adminRights != nil) { + return context.engine.peers.getChannelBoostStatus(peerId: peerId) + } else if case .group = channel.info { + return context.engine.peers.getChannelBoostStatus(peerId: peerId) + } } + return .single(nil) } |> deliverOnMainQueue).start(next: { [weak self] boostStatus in guard let self else { @@ -5688,6 +5732,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } else if let channel = peer as? TelegramChannel { if let cachedData = strongSelf.data?.cachedData as? CachedChannelData { + if case .group = channel.info { + //TODO:localized + items.append(.action(ContextMenuActionItem(text: "Boost Group", badge: ContextMenuActionBadge(value: "NEW", color: .accent, style: .label), icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openBoost() + }))) + } + if case .broadcast = channel.info, channel.hasPermission(.editStories) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_Channel_ArchivedStories, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) @@ -6907,6 +6962,35 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro controller.push(statsController) } + private func openBoost() { + guard let peer = self.data?.peer, let channel = peer as? TelegramChannel, let controller = self.controller else { + return + } + + if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) { + let boostsController = channelStatsController(context: self.context, updatedPresentationData: controller.updatedPresentationData, peerId: self.peerId, section: .boosts, boostStatus: self.boostStatus) + controller.push(boostsController) + } else { + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: self.peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { [weak self] boostStatus, myBoostStatus in + guard let self, let controller = self.controller, let boostStatus, let myBoostStatus else { + return + } + let boostController = PremiumBoostLevelsScreen( + context: self.context, + peerId: controller.peerId, + mode: .user(mode: .current), + status: boostStatus, + myBoostStatus: myBoostStatus + ) + controller.push(boostController) + }) + } + } + private func openVoiceChatOptions(defaultJoinAsPeerId: PeerId?, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil) { guard let chatPeer = self.data?.peer else { return diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 61e0ba5165..6cb9794bee 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -732,6 +732,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, hideTranslationPanel: { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { + }, openBoostToUnrestrict: { }, updateHistoryFilter: { _ in }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD index 7d60b8c35c..17f4fe21d2 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD @@ -47,6 +47,8 @@ swift_library( "//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen", "//submodules/TelegramUI/Components/Settings/WallpaperGridScreen", "//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/GroupStickerPackSetupController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/BackButton.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/BackButton.swift new file mode 100644 index 0000000000..271cae3d65 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/BackButton.swift @@ -0,0 +1,268 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import ContextUI +import TelegramPresentationData +import Display + +enum PeerInfoHeaderNavigationButtonKey { + case back + case edit + case done + case cancel + case select + case selectionDone + case search + case editPhoto + case editVideo + case more + case qrCode + case moreToSearch + case postStory +} + +final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { + let containerNode: ContextControllerSourceNode + let contextSourceNode: ContextReferenceContentNode + private let textNode: ImmediateTextNode + private let iconNode: ASImageNode + private let backIconLayer: SimpleShapeLayer + private let backgroundNode: NavigationBackgroundNode + + private var key: PeerInfoHeaderNavigationButtonKey? + + private var contentsColor: UIColor = .white + private var canBeExpanded: Bool = false + + var action: ((ASDisplayNode, ContextGesture?) -> Void)? + + init() { + self.contextSourceNode = ContextReferenceContentNode() + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + + self.textNode = ImmediateTextNode() + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + + self.backIconLayer = SimpleShapeLayer() + self.backIconLayer.lineWidth = 3.0 + self.backIconLayer.lineCap = .round + self.backIconLayer.lineJoin = .round + self.backIconLayer.strokeColor = UIColor.white.cgColor + self.backIconLayer.fillColor = nil + self.backIconLayer.isHidden = true + self.backIconLayer.path = try? convertSvgPath("M10.5,2 L1.5,11 L10.5,20 ") + + self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true) + + super.init(pointerStyle: .insetRectangle(-8.0, 2.0)) + + self.isAccessibilityElement = true + self.accessibilityTraits = .button + + self.containerNode.addSubnode(self.contextSourceNode) + self.contextSourceNode.addSubnode(self.backgroundNode) + self.contextSourceNode.addSubnode(self.textNode) + self.contextSourceNode.addSubnode(self.iconNode) + self.contextSourceNode.layer.addSublayer(self.backIconLayer) + + self.addSubnode(self.containerNode) + + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self else { + return + } + strongSelf.action?(strongSelf.contextSourceNode, gesture) + } + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc private func pressed() { + self.action?(self.contextSourceNode, nil) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + var boundingRect = self.bounds + if self.textNode.alpha != 0.0 { + boundingRect = boundingRect.union(self.textNode.frame) + } + boundingRect = boundingRect.insetBy(dx: -8.0, dy: -4.0) + if boundingRect.contains(point) { + return super.hitTest(self.bounds.center, with: event) + } else { + return nil + } + } + + func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) { + self.contentsColor = contentsColor + self.canBeExpanded = canBeExpanded + + self.backgroundNode.updateColor(color: backgroundColor, transition: transition) + + transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) + transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) + transition.updateStrokeColor(layer: self.backIconLayer, strokeColor: self.contentsColor) + + switch self.key { + case .back: + transition.updateAlpha(layer: self.textNode.layer, alpha: canBeExpanded ? 1.0 : 0.0) + transition.updateTransformScale(node: self.textNode, scale: canBeExpanded ? 1.0 : 0.001) + + var iconTransform = CATransform3DIdentity + iconTransform = CATransform3DScale(iconTransform, canBeExpanded ? 1.0 : 0.8, canBeExpanded ? 1.0 : 0.8, 1.0) + iconTransform = CATransform3DTranslate(iconTransform, canBeExpanded ? -7.0 : 0.0, 0.0, 0.0) + transition.updateTransform(node: self.iconNode, transform: CATransform3DGetAffineTransform(iconTransform)) + + transition.updateTransform(layer: self.backIconLayer, transform: CATransform3DGetAffineTransform(iconTransform)) + transition.updateLineWidth(layer: self.backIconLayer, lineWidth: canBeExpanded ? 3.0 : 2.075) + default: + break + } + } + + func update(key: PeerInfoHeaderNavigationButtonKey, presentationData: PresentationData, height: CGFloat) -> CGSize { + let transition: ContainedViewLayoutTransition = .immediate + + var iconOffset = CGPoint() + switch key { + case .back: + iconOffset = CGPoint(x: -1.0, y: 0.0) + default: + break + } + + let textSize: CGSize + if self.key != key { + self.key = key + + let text: String + var accessibilityText: String + var icon: UIImage? + var isBold = false + var isGestureEnabled = false + switch key { + case .back: + text = presentationData.strings.Common_Back + accessibilityText = presentationData.strings.Common_Back + icon = NavigationBar.backArrowImage(color: .white) + case .edit: + text = presentationData.strings.Common_Edit + accessibilityText = text + case .cancel: + text = presentationData.strings.Common_Cancel + accessibilityText = text + isBold = false + case .done, .selectionDone: + text = presentationData.strings.Common_Done + accessibilityText = text + isBold = true + case .select: + text = presentationData.strings.Common_Select + accessibilityText = text + case .search: + text = "" + accessibilityText = presentationData.strings.Common_Search + icon = nil// PresentationResourcesRootController.navigationCompactSearchIcon(presentationData.theme) + case .editPhoto: + text = presentationData.strings.Settings_EditPhoto + accessibilityText = text + case .editVideo: + text = presentationData.strings.Settings_EditVideo + accessibilityText = text + case .more: + text = "" + accessibilityText = presentationData.strings.Common_More + icon = nil// PresentationResourcesRootController.navigationMoreCircledIcon(presentationData.theme) + isGestureEnabled = true + case .qrCode: + text = "" + accessibilityText = presentationData.strings.PeerInfo_QRCode_Title + icon = PresentationResourcesRootController.navigationQrCodeIcon(presentationData.theme) + case .moreToSearch: + text = "" + accessibilityText = "" + case .postStory: + text = "" + accessibilityText = presentationData.strings.Story_Privacy_PostStory + icon = PresentationResourcesRootController.navigationPostStoryIcon(presentationData.theme) + } + self.accessibilityLabel = accessibilityText + self.containerNode.isGestureEnabled = isGestureEnabled + + let font: UIFont = isBold ? Font.semibold(17.0) : Font.regular(17.0) + + self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: .white) + transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) + self.iconNode.image = icon + transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) + + self.iconNode.isHidden = false + + textSize = self.textNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + } else { + textSize = self.textNode.bounds.size + } + + let inset: CGFloat = 0.0 + var textInset: CGFloat = 0.0 + switch key { + case .back: + textInset += 11.0 + default: + break + } + + let resultSize: CGSize + + let textFrame = CGRect(origin: CGPoint(x: inset + textInset, y: floor((height - textSize.height) / 2.0)), size: textSize) + self.textNode.position = textFrame.center + self.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + + if let image = self.iconNode.image { + let iconFrame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size).offsetBy(dx: iconOffset.x, dy: iconOffset.y) + self.iconNode.position = iconFrame.center + self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) + + if case .back = key { + self.backIconLayer.position = iconFrame.center + self.backIconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) + + self.iconNode.isHidden = true + self.backIconLayer.isHidden = false + } else { + self.iconNode.isHidden = false + self.backIconLayer.isHidden = true + } + + let size = CGSize(width: image.size.width + inset * 2.0, height: height) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) + self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) + resultSize = size + } else { + let size = CGSize(width: textSize.width + inset * 2.0, height: height) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) + self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) + resultSize = size + } + + let diameter: CGFloat = 32.0 + let backgroundWidth: CGFloat + if self.iconNode.image != nil { + backgroundWidth = diameter + } else { + backgroundWidth = max(diameter, resultSize.width + 12.0 * 2.0) + } + let backgroundFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - backgroundWidth) * 0.5), y: floor((resultSize.height - diameter) * 0.5)), size: CGSize(width: backgroundWidth, height: diameter)) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: diameter * 0.5, transition: transition) + + self.hitTestSlop = UIEdgeInsets(top: -2.0, left: -12.0, bottom: -2.0, right: -12.0) + + return resultSize + } +} diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index c7b18fc038..11ed2f1869 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -34,6 +34,9 @@ import MediaPickerUI import WallpaperGalleryScreen import WallpaperGridScreen import BoostLevelIconComponent +import BundleIconComponent +import Markdown +import GroupStickerPackSetupController private final class EmojiActionIconComponent: Component { let context: AccountContext @@ -160,12 +163,14 @@ final class ChannelAppearanceScreenComponent: Component { private final class ContentsData { let peer: EnginePeer? let peerWallpaper: TelegramWallpaper? + let peerEmojiPack: StickerPackCollectionInfo? let subscriberCount: Int? let availableThemes: [TelegramTheme] - init(peer: EnginePeer?, peerWallpaper: TelegramWallpaper?, subscriberCount: Int?, availableThemes: [TelegramTheme]) { + init(peer: EnginePeer?, peerWallpaper: TelegramWallpaper?, peerEmojiPack: StickerPackCollectionInfo?, subscriberCount: Int?, availableThemes: [TelegramTheme]) { self.peer = peer self.peerWallpaper = peerWallpaper + self.peerEmojiPack = peerEmojiPack self.subscriberCount = subscriberCount self.availableThemes = availableThemes } @@ -175,15 +180,17 @@ final class ChannelAppearanceScreenComponent: Component { context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId), + TelegramEngine.EngineData.Item.Peer.EmojiPack(id: peerId), TelegramEngine.EngineData.Item.Peer.Wallpaper(id: peerId) ), telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) ) |> map { peerData, cloudThemes -> ContentsData in - let (peer, subscriberCount, wallpaper) = peerData + let (peer, subscriberCount, emojiPack, wallpaper) = peerData return ContentsData( peer: peer, peerWallpaper: wallpaper, + peerEmojiPack: emojiPack, subscriberCount: subscriberCount, availableThemes: cloudThemes ) @@ -211,6 +218,7 @@ final class ChannelAppearanceScreenComponent: Component { static let backgroundFileId = Changes(rawValue: 1 << 3) static let emojiStatus = Changes(rawValue: 1 << 4) static let wallpaper = Changes(rawValue: 1 << 5) + static let emojiPack = Changes(rawValue: 1 << 6) } var nameColor: PeerNameColor @@ -219,31 +227,49 @@ final class ChannelAppearanceScreenComponent: Component { var backgroundFileId: Int64? var emojiStatus: PeerEmojiStatus? var wallpaper: TelegramWallpaper? + var emojiPack: StickerPackCollectionInfo? var changes: Changes - init(nameColor: PeerNameColor, profileColor: PeerNameColor?, replyFileId: Int64?, backgroundFileId: Int64?, emojiStatus: PeerEmojiStatus?, wallpaper: TelegramWallpaper?, changes: Changes) { + init( + nameColor: PeerNameColor, + profileColor: PeerNameColor?, + replyFileId: Int64?, + backgroundFileId: Int64?, + emojiStatus: PeerEmojiStatus?, + wallpaper: TelegramWallpaper?, + emojiPack: StickerPackCollectionInfo?, + changes: Changes + ) { self.nameColor = nameColor self.profileColor = profileColor self.replyFileId = replyFileId self.backgroundFileId = backgroundFileId self.emojiStatus = emojiStatus self.wallpaper = wallpaper + self.emojiPack = emojiPack self.changes = changes } } final class View: UIView, UIScrollViewDelegate { + private let topOverscrollLayer = SimpleLayer() private let scrollView: ScrollView private let actionButton = ComponentView() private let bottomPanelBackgroundView: BlurredBackgroundView private let bottomPanelSeparator: SimpleLayer + private let backButton = PeerInfoHeaderNavigationButton() + private let navigationTitle = ComponentView() + + private let previewSection = ComponentView() + private let boostSection = ComponentView() + private let bannerSection = ComponentView() private let replySection = ComponentView() private let wallpaperSection = ComponentView() - private let bannerSection = ComponentView() private let resetColorSection = ComponentView() private let emojiStatusSection = ComponentView() + private let emojiPackSection = ComponentView() private var chatPreviewItemNode: PeerNameColorChatPreviewItemNode? @@ -265,6 +291,7 @@ final class ChannelAppearanceScreenComponent: Component { private var updatedPeerProfileEmoji: Int64?? private var updatedPeerStatus: PeerEmojiStatus?? private var updatedPeerWallpaper: WallpaperSelectionResult? + private var updatedPeerEmojiPack: StickerPackCollectionInfo?? private var temporaryPeerWallpaper: TelegramWallpaper? private var requiredBoostSubject: BoostSubject? @@ -305,8 +332,16 @@ final class ChannelAppearanceScreenComponent: Component { self.scrollView.delegate = self self.addSubview(self.scrollView) + self.scrollView.layer.addSublayer(self.topOverscrollLayer) + self.addSubview(self.bottomPanelBackgroundView) self.layer.addSublayer(self.bottomPanelSeparator) + + self.backButton.action = { [weak self] _, _ in + if let self, let controller = self.environment?.controller() { + controller.navigationController?.popViewController(animated: true) + } + } } required init?(coder: NSCoder) { @@ -333,8 +368,8 @@ final class ChannelAppearanceScreenComponent: Component { } if !resolvedState.changes.isEmpty { - if let premiumConfiguration = self.premiumConfiguration, let requiredBoostSubject = self.requiredBoostSubject{ - let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration) + if let premiumConfiguration = self.premiumConfiguration, let requiredBoostSubject = self.requiredBoostSubject { + let requiredLevel = requiredBoostSubject.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { return true } @@ -366,6 +401,7 @@ final class ChannelAppearanceScreenComponent: Component { self.updateScrolling(transition: .immediate) } + var scrolledUp = true private func updateScrolling(transition: Transition) { let navigationAlphaDistance: CGFloat = 16.0 let navigationAlpha: CGFloat = max(0.0, min(1.0, self.scrollView.contentOffset.y / navigationAlphaDistance)) @@ -374,6 +410,24 @@ final class ChannelAppearanceScreenComponent: Component { transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha) } + var scrolledUp = false + if navigationAlpha < 0.5 { + scrolledUp = true + } else if navigationAlpha > 0.5 { + scrolledUp = false + } + + if self.scrolledUp != scrolledUp { + self.scrolledUp = scrolledUp + if !self.isUpdating { + self.state?.updated() + } + } + + if let navigationTitleView = self.navigationTitle.view { + transition.setAlpha(view: navigationTitleView, alpha: navigationAlpha) + } + let bottomNavigationAlphaDistance: CGFloat = 16.0 let bottomNavigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentSize.height - self.scrollView.bounds.maxY) / bottomNavigationAlphaDistance)) @@ -442,6 +496,16 @@ final class ChannelAppearanceScreenComponent: Component { changes.insert(.emojiStatus) } + let emojiPack: StickerPackCollectionInfo? + if case let .some(value) = self.updatedPeerEmojiPack { + emojiPack = value + } else { + emojiPack = contentsData.peerEmojiPack + } + if emojiPack != contentsData.peerEmojiPack { + changes.insert(.emojiPack) + } + let wallpaper: TelegramWallpaper? if let updatedPeerWallpaper = self.updatedPeerWallpaper { switch updatedPeerWallpaper { @@ -464,6 +528,7 @@ final class ChannelAppearanceScreenComponent: Component { backgroundFileId: backgroundFileId, emojiStatus: emojiStatus, wallpaper: wallpaper, + emojiPack: emojiPack, changes: changes ) } @@ -476,7 +541,7 @@ final class ChannelAppearanceScreenComponent: Component { return } - let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration) + let requiredLevel = requiredBoostSubject.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { self.displayBoostLevels(subject: requiredBoostSubject) return @@ -520,6 +585,13 @@ final class ChannelAppearanceScreenComponent: Component { return .generic }) } + if resolvedState.changes.contains(.emojiPack) { + signals.append(component.context.engine.peers.updateGroupSpecificEmojiset(peerId: component.peerId, info: resolvedState.emojiPack) + |> ignoreValues + |> mapError { _ -> ApplyError in + return .generic + }) + } if resolvedState.changes.contains(.wallpaper) { if let updatedPeerWallpaper { switch updatedPeerWallpaper { @@ -569,39 +641,33 @@ final class ChannelAppearanceScreenComponent: Component { }) } - private func displayBoostLevels(subject: BoostSubject) { - guard let component = self.component else { + private func displayBoostLevels(subject: BoostSubject?) { + guard let component = self.component, let status = self.boostStatus else { return } - let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in - guard let self, let component = self.component, let peer, let status = self.boostStatus else { - return - } - let controller = PremiumBoostLevelsScreen( - context: component.context, - peer: peer, - subject: subject, - status: status, - openStats: { [weak self] in - guard let self else { - return - } - self.openBoostStats() - }, - openGift: { [weak self] in - guard let self, let component = self.component else { - return - } - let controller = createGiveawayController(context: component.context, peerId: component.peerId, subject: .generic) - self.environment?.controller()?.push(controller) + let controller = PremiumBoostLevelsScreen( + context: component.context, + peerId: component.peerId, + mode: .owner(subject: subject), + status: status, + openStats: { [weak self] in + guard let self else { + return } - ) - self.environment?.controller()?.push(controller) - - HapticFeedback().impact(.light) - }) + self.openBoostStats() + }, + openGift: { [weak self] in + guard let self, let component = self.component else { + return + } + let controller = createGiveawayController(context: component.context, peerId: component.peerId, subject: .generic) + self.environment?.controller()?.push(controller) + } + ) + self.environment?.controller()?.push(controller) + + HapticFeedback().impact(.light) } private func openBoostStats() { @@ -617,8 +683,8 @@ final class ChannelAppearanceScreenComponent: Component { return } let level = boostStatus.level - let requiredWallpaperLevel = Int(BoostSubject.wallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) - let requiredCustomWallpaperLevel = Int(BoostSubject.customWallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) + let requiredWallpaperLevel = Int(BoostSubject.wallpaper.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration)) + let requiredCustomWallpaperLevel = Int(BoostSubject.customWallpaper.requiredLevel(group: self.isGroup, context: component.context, configuration: premiumConfiguration)) let selectedWallpaper = resolvedState.wallpaper @@ -653,12 +719,25 @@ final class ChannelAppearanceScreenComponent: Component { self.environment?.controller()?.push(controller) } + private func openEmojiPackSetup() { + guard let component = self.component, let environment = self.environment, let resolvedState = self.resolveState() else { + return + } + let controller = groupStickerPackSetupController(context: component.context, peerId: component.peerId, emoji: true, currentPackInfo: resolvedState.emojiPack, completion: { [weak self] emojiPack in + if let self { + self.updatedPeerEmojiPack = emojiPack + self.state?.updated(transition: .spring(duration: 0.4)) + } + }) + environment.controller()?.push(controller) + } + private enum EmojiSetupSubject { case reply case profile case status } - + private var previousEmojiSetupTimestamp: Double? private func openEmojiSetup(sourceView: UIView, currentFileId: Int64?, color: UIColor?, subject: EmojiSetupSubject) { guard let component = self.component, let environment = self.environment else { @@ -755,6 +834,16 @@ final class ChannelAppearanceScreenComponent: Component { environment.controller()?.present(controller, in: .window(.root)) } + private var isGroup: Bool { + guard let contentsData = self.contentsData, let peer = contentsData.peer else { + return false + } + if case let .channel(channel) = peer, case .group = channel.info { + return true + } + return false + } + func update(component: ChannelAppearanceScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.isUpdating = true defer { @@ -818,6 +907,7 @@ final class ChannelAppearanceScreenComponent: Component { } self.boostLevel = boostStatus?.level self.boostStatus = boostStatus + if !self.isUpdating { self.state?.updated(transition: .immediate) } @@ -829,12 +919,7 @@ final class ChannelAppearanceScreenComponent: Component { } var requiredBoostSubjects: [BoostSubject] = [.nameColors(colors: resolvedState.nameColor)] - - let replyIconLevel = Int(BoostSubject.nameIcon.requiredLevel(context: component.context, configuration: premiumConfiguration)) - let profileIconLevel = Int(BoostSubject.profileIcon.requiredLevel(context: component.context, configuration: premiumConfiguration)) - let emojiStatusLevel = Int(BoostSubject.emojiStatus.requiredLevel(context: component.context, configuration: premiumConfiguration)) - let themeLevel = Int(BoostSubject.wallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) - + let replyFileId = resolvedState.replyFileId if replyFileId != nil { requiredBoostSubjects.append(.nameIcon) @@ -854,6 +939,12 @@ final class ChannelAppearanceScreenComponent: Component { if emojiStatus != nil { requiredBoostSubjects.append(.emojiStatus) } + + let emojiPack = resolvedState.emojiPack + if emojiPack != nil { + requiredBoostSubjects.append(.emojiPack) + } + let statusFileId = emojiStatus?.fileId let cloudThemes: [PresentationThemeReference] = contentsData.availableThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? component.context.account.id : nil)) } @@ -911,6 +1002,7 @@ final class ChannelAppearanceScreenComponent: Component { } } + var isGroup = false if case let .user(user) = peer { peer = .user(user .withUpdatedNameColor(resolvedState.nameColor) @@ -927,17 +1019,73 @@ final class ChannelAppearanceScreenComponent: Component { .withUpdatedBackgroundEmojiId(replyFileId) .withUpdatedProfileBackgroundEmojiId(backgroundFileId) ) + if case .group = channel.info { + isGroup = true + } } + let replyIconLevel = Int(BoostSubject.nameIcon.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let profileIconLevel = Int(BoostSubject.profileIcon.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let emojiStatusLevel = Int(BoostSubject.emojiStatus.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let emojiPackLevel = Int(BoostSubject.emojiPack.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let themeLevel = Int(BoostSubject.wallpaper.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration)) + let requiredBoostSubject: BoostSubject - if let maxBoostSubject = requiredBoostSubjects.max(by: { $0.requiredLevel(context: component.context, configuration: premiumConfiguration) < $1.requiredLevel(context: component.context, configuration: premiumConfiguration) }) { + if let maxBoostSubject = requiredBoostSubjects.max(by: { $0.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration) < $1.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration) }) { requiredBoostSubject = maxBoostSubject } else { requiredBoostSubject = .nameColors(colors: resolvedState.nameColor) } self.requiredBoostSubject = requiredBoostSubject - let topInset: CGFloat = 24.0 + + let headerColor: UIColor + if let profileColor { + let headerBackgroundColors = component.context.peerNameColors.getProfile(profileColor, dark: environment.theme.overallDarkAppearance, subject: .background) + headerColor = headerBackgroundColors.secondary ?? headerBackgroundColors.main + } else { + headerColor = .clear + } + self.topOverscrollLayer.backgroundColor = headerColor.cgColor + + let backSize = self.backButton.update(key: .back, presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, height: 44.0) + var scrolledUp = self.scrolledUp + if profileColor == nil { + scrolledUp = false + } + + if let controller = self.environment?.controller() as? ChannelAppearanceScreen { + controller.statusBar.updateStatusBarStyle(scrolledUp ? .White : .Ignore, animated: true) + } + + self.backButton.updateContentsColor(backgroundColor: scrolledUp ? UIColor(white: 0.0, alpha: 0.1) : .clear, contentsColor: scrolledUp ? .white : environment.theme.rootController.navigationBar.accentTextColor, canBeExpanded: !scrolledUp, transition: .animated(duration: 0.2, curve: .easeInOut)) + self.backButton.frame = CGRect(origin: CGPoint(x: 16.0, y: 54.0), size: backSize) + if self.backButton.view.superview == nil { + if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { + navigationBar.view.addSubview(self.backButton.view) + } + } + + //TODO:localize + let navigationTitleSize = self.navigationTitle.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "Appearance", font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center + )), + environment: {}, + containerSize: availableSize + ) + let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.statusBarHeight + floor((environment.navigationHeight - environment.statusBarHeight - navigationTitleSize.height) / 2.0)), size: navigationTitleSize) + if let navigationTitleView = self.navigationTitle.view { + if navigationTitleView.superview == nil { + if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { + navigationBar.view.addSubview(navigationTitleView) + } + } + transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame) + } + let bottomContentInset: CGFloat = 24.0 let bottomInset: CGFloat = 8.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left @@ -946,115 +1094,83 @@ final class ChannelAppearanceScreenComponent: Component { let listItemParams = ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true) var contentHeight: CGFloat = 0.0 - contentHeight += environment.navigationHeight - contentHeight += topInset let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - - let messageItem = PeerNameColorChatPreviewItem.MessageItem( - outgoing: false, - peerId: EnginePeer.Id(namespace: peer.id.namespace, id: PeerId.Id._internalFromInt64Value(0)), - author: peer.compactDisplayTitle, - photo: peer.profileImageRepresentations, - nameColor: resolvedState.nameColor, - backgroundEmojiId: replyFileId, - reply: (peer.compactDisplayTitle, environment.strings.Channel_Appearance_ExampleReplyText), - linkPreview: (environment.strings.Channel_Appearance_ExampleLinkWebsite, environment.strings.Channel_Appearance_ExampleLinkTitle, environment.strings.Channel_Appearance_ExampleLinkText), - text: environment.strings.Channel_Appearance_ExampleText - ) - - var replyLogoContents: [AnyComponentWithIdentity] = [] - replyLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_Appearance_NameIcon, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 0 - )))) - if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelNameIconLevel { - replyLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( - strings: environment.strings, - level: replyIconLevel - )))) - } - - var chatPreviewTheme: PresentationTheme = environment.theme - var chatPreviewWallpaper: TelegramWallpaper = presentationData.chatWallpaper - if let updatedWallpaper = self.updatedPeerWallpaper, case .remove = updatedWallpaper { - } else if let temporaryPeerWallpaper = self.temporaryPeerWallpaper { - chatPreviewWallpaper = temporaryPeerWallpaper - } else if let resolvedCurrentTheme = self.resolvedCurrentTheme { - chatPreviewTheme = resolvedCurrentTheme.theme - if let wallpaper = resolvedCurrentTheme.wallpaper { - chatPreviewWallpaper = wallpaper - } - } else if let initialWallpaper = contentsData.peerWallpaper, !initialWallpaper.isEmoticon { - chatPreviewWallpaper = initialWallpaper - } - - let replySectionSize = self.replySection.update( + + let previewSectionSize = self.previewSection.update( transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + background: .none(clipped: false), header: nil, - footer: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_Appearance_NameColorFooter, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 - )), + footer: nil, items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( - itemGenerator: PeerNameColorChatPreviewItem( + itemGenerator: PeerNameColorProfilePreviewItem( context: component.context, - theme: chatPreviewTheme, - componentTheme: chatPreviewTheme, - strings: environment.strings, - sectionId: 0, - fontSize: presentationData.chatFontSize, - chatBubbleCorners: presentationData.chatBubbleCorners, - wallpaper: chatPreviewWallpaper, - dateTimeFormat: environment.dateTimeFormat, - nameDisplayOrder: presentationData.nameDisplayOrder, - messageItems: [messageItem] - ), - params: listItemParams - ))), - AnyComponentWithIdentity(id: 1, component: AnyComponent(ListItemComponentAdaptor( - itemGenerator: PeerNameColorItem( theme: environment.theme, - colors: component.context.peerNameColors, - isProfile: false, - currentColor: resolvedState.nameColor, - updated: { [weak self] value in - guard let self else { - return - } - self.updatedPeerNameColor = value - self.state?.updated(transition: .spring(duration: 0.4)) + componentTheme: environment.theme, + strings: environment.strings, + topInset: environment.statusBarHeight, + sectionId: 0, + peer: peer, + subtitleString: contentsData.subscriberCount.flatMap { + isGroup ? environment.strings.Conversation_StatusMembers(Int32($0)) : environment.strings.Conversation_StatusSubscribers(Int32($0)) }, - sectionId: 0 + files: self.cachedIconFiles, + nameDisplayOrder: presentationData.nameDisplayOrder ), - params: listItemParams + params: ListViewItemLayoutParams(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true) ))), - AnyComponentWithIdentity(id: 2, component: AnyComponent(ListActionItemComponent( + ] + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 1000.0) + ) + let previewSectionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: previewSectionSize) + if let previewSectionView = self.previewSection.view { + if previewSectionView.superview == nil { + self.scrollView.addSubview(previewSectionView) + } + transition.setFrame(view: previewSectionView, frame: previewSectionFrame) + } + contentHeight += previewSectionSize.height + contentHeight += sectionSpacing - 15.0 + + //TODO:localize + var boostContents: [AnyComponentWithIdentity] = [] + boostContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent( + name: "Premium/Boost", + tintColor: environment.theme.list.itemAccentColor + )))) + boostContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( + text: .markdown( + text: isGroup ? "Members of your group can **boost** it so that it levels up and unlocks these features." : "Subscribers of your channel can **boost** it so that it levels up and unlocks these features.", + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize / 17.0 * 14.0), textColor: environment.theme.list.itemPrimaryTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize / 17.0 * 14.0), textColor: environment.theme.list.itemPrimaryTextColor), + link: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize / 17.0 * 14.0), textColor: environment.theme.list.itemAccentColor), + linkAttribute: { _ in + return nil + } + ) + ), + maximumNumberOfLines: 0 + )))) + let boostSectionSize = self.boostSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + background: .all, + header: nil, + footer: nil, + items: [ + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, - title: AnyComponent(HStack(replyLogoContents, spacing: 6.0)), - icon: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( - context: component.context, - color: component.context.peerNameColors.get(resolvedState.nameColor, dark: environment.theme.overallDarkAppearance).main, - fileId: replyFileId, - file: replyFileId.flatMap { self.cachedIconFiles[$0] } - ))), - action: { [weak self] view in - guard let self, let resolvedState = self.resolveState(), let view = view as? ListActionItemComponent.View, let iconView = view.iconView else { - return - } - - self.openEmojiSetup(sourceView: iconView, currentFileId: resolvedState.replyFileId, color: component.context.peerNameColors.get(resolvedState.nameColor, dark: environment.theme.overallDarkAppearance).main, subject: .reply) + title: AnyComponent(HStack(boostContents, spacing: 12.0)), + icon: nil, + action: { [weak self] _ in + self?.displayBoostLevels(subject: nil) } ))) ] @@ -1062,114 +1178,15 @@ final class ChannelAppearanceScreenComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) ) - let replySectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: replySectionSize) - if let replySectionView = self.replySection.view { - if replySectionView.superview == nil { - self.scrollView.addSubview(replySectionView) + let boostSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: boostSectionSize) + if let boostSectionView = self.boostSection.view { + if boostSectionView.superview == nil { + self.scrollView.addSubview(boostSectionView) } - transition.setFrame(view: replySectionView, frame: replySectionFrame) - } - contentHeight += replySectionSize.height - - contentHeight += sectionSpacing - - if !chatThemes.isEmpty { - var wallpaperLogoContents: [AnyComponentWithIdentity] = [] - wallpaperLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_Appearance_Wallpaper, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 0 - )))) - if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelCustomWallpaperLevel { - wallpaperLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( - strings: environment.strings, - level: themeLevel - )))) - } - - var currentTheme = self.currentTheme - var selectedWallpaper: TelegramWallpaper? - if currentTheme == nil, let wallpaper = resolvedState.wallpaper, !wallpaper.isEmoticon { - let theme: PresentationThemeReference = .builtin(.day) - currentTheme = theme - selectedWallpaper = wallpaper - } - - let wallpaperSectionSize = self.wallpaperSection.update( - transition: transition, - component: AnyComponent(ListSectionComponent( - theme: environment.theme, - header: nil, - footer: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_Appearance_WallpaperFooter, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 - )), - items: [ - AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( - itemGenerator: ThemeCarouselThemeItem( - context: component.context, - theme: environment.theme, - strings: environment.strings, - sectionId: 0, - themes: chatThemes, - hasNoTheme: true, - animatedEmojiStickers: component.context.animatedEmojiStickers, - themeSpecificAccentColors: [:], - themeSpecificChatWallpapers: [:], - nightMode: environment.theme.overallDarkAppearance, - channelMode: true, - selectedWallpaper: selectedWallpaper, - currentTheme: currentTheme, - updatedTheme: { [weak self] value in - guard let self, value != .builtin(.day) else { - return - } - self.currentTheme = value - self.temporaryPeerWallpaper = nil - if let value { - self.updatedPeerWallpaper = .emoticon(value.emoticon ?? "") - } else { - self.updatedPeerWallpaper = .remove - } - self.state?.updated(transition: .spring(duration: 0.4)) - }, - contextAction: nil - ), - params: listItemParams - ))), - AnyComponentWithIdentity(id: 1, component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - title: AnyComponent(HStack(wallpaperLogoContents, spacing: 6.0)), - icon: nil, - action: { [weak self] view in - guard let self else { - return - } - self.openCustomWallpaperSetup() - } - ))) - ] - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) - ) - let wallpaperSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: wallpaperSectionSize) - if let wallpaperSectionView = self.wallpaperSection.view { - if wallpaperSectionView.superview == nil { - self.scrollView.addSubview(wallpaperSectionView) - } - transition.setFrame(view: wallpaperSectionView, frame: wallpaperSectionFrame) - } - contentHeight += wallpaperSectionSize.height - contentHeight += sectionSpacing + transition.setFrame(view: boostSectionView, frame: boostSectionFrame) } + contentHeight += boostSectionSize.height + contentHeight += sectionSpacing - 8.0 var profileLogoContents: [AnyComponentWithIdentity] = [] profileLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( @@ -1180,55 +1197,27 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelProfileIconLevel { + if let boostLevel = self.boostLevel, boostLevel < (isGroup ? premiumConfiguration.minGroupProfileIconLevel : premiumConfiguration.minChannelProfileIconLevel) { profileLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, level: profileIconLevel )))) } - - let bannerBackground: ListSectionComponent.Background - if profileColor != nil { - bannerBackground = .range(from: 1, corners: DynamicCornerRadiusView.Corners(minXMinY: 0.0, maxXMinY: 0.0, minXMaxY: 11.0, maxXMaxY: 11.0)) - } else { - bannerBackground = .range(from: 1, corners: DynamicCornerRadiusView.Corners(minXMinY: 11.0, maxXMinY: 11.0, minXMaxY: 11.0, maxXMaxY: 11.0)) - } let bannerSectionSize = self.bannerSection.update( transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, - background: bannerBackground, - header: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_Appearance_ProfileHeader, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 - )), + background: .all, + header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.Channel_Appearance_ProfileFooter, + string: isGroup ? "Choose a color and a logo for the group's profile." : environment.strings.Channel_Appearance_ProfileFooter, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), maximumNumberOfLines: 0 )), items: [ - AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( - itemGenerator: PeerNameColorProfilePreviewItem( - context: component.context, - theme: environment.theme, - componentTheme: environment.theme, - strings: environment.strings, - sectionId: 0, - peer: peer, - subtitleString: contentsData.subscriberCount.flatMap { environment.strings.Conversation_StatusSubscribers(Int32($0)) }, - files: self.cachedIconFiles, - nameDisplayOrder: presentationData.nameDisplayOrder - ), - params: listItemParams - ))), AnyComponentWithIdentity(id: 1, component: AnyComponent(ListItemComponentAdaptor( itemGenerator: PeerNameColorItem( theme: environment.theme, @@ -1281,23 +1270,7 @@ final class ChannelAppearanceScreenComponent: Component { } contentHeight += bannerSectionSize.height contentHeight += sectionSpacing - - var emojiStatusContents: [AnyComponentWithIdentity] = [] - emojiStatusContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_Appearance_Status, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 0 - )))) - if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelEmojiStatusLevel { - emojiStatusContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( - strings: environment.strings, - level: emojiStatusLevel - )))) - } - + let resetColorSectionSize = self.resetColorSection.update( transition: transition, component: AnyComponent(ListSectionComponent( @@ -1350,6 +1323,92 @@ final class ChannelAppearanceScreenComponent: Component { contentHeight += sectionSpacing } + if isGroup { + //TODO:localize + var emojiPackContents: [AnyComponentWithIdentity] = [] + emojiPackContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Group Emoji Pack", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + )))) + if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minGroupEmojiPackLevel { + emojiPackContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( + strings: environment.strings, + level: emojiPackLevel + )))) + } + + + var emojiPackFile: TelegramMediaFile? + if let thumbnail = emojiPack?.thumbnail { + emojiPackFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: thumbnail.immediateThumbnailData, mimeType: "", size: nil, attributes: []) + } + + let emojiPackSectionSize = self.emojiPackSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: nil, + footer: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Choose an emoji pack that will be available to all members within the group.", + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: environment.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + items: [ + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(HStack(emojiPackContents, spacing: 6.0)), + icon: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( + context: component.context, + color: environment.theme.list.itemAccentColor, + fileId: emojiPack?.thumbnailFileId, + file: emojiPackFile + ))), + action: { [weak self] view in + guard let self, let resolvedState = self.resolveState() else { + return + } + let _ = resolvedState + self.openEmojiPackSetup() + } + ))) + ] + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + let emojiPackSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: emojiPackSectionSize) + if let emojiPackSectionView = self.emojiPackSection.view { + if emojiPackSectionView.superview == nil { + self.scrollView.addSubview(emojiPackSectionView) + } + transition.setFrame(view: emojiPackSectionView, frame: emojiPackSectionFrame) + } + contentHeight += emojiPackSectionSize.height + contentHeight += sectionSpacing + } + + var emojiStatusContents: [AnyComponentWithIdentity] = [] + emojiStatusContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: isGroup ? "Group Emoji Status" : environment.strings.Channel_Appearance_Status, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + )))) + if let boostLevel = self.boostLevel, boostLevel < (isGroup ? premiumConfiguration.minGroupEmojiStatusLevel : premiumConfiguration.minChannelEmojiStatusLevel) { + emojiStatusContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( + strings: environment.strings, + level: emojiStatusLevel + )))) + } let emojiStatusSectionSize = self.emojiStatusSection.update( transition: transition, component: AnyComponent(ListSectionComponent( @@ -1357,7 +1416,7 @@ final class ChannelAppearanceScreenComponent: Component { header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.Channel_Appearance_StatusFooter, + string: isGroup ? "Choose a status that will be shown next to the group's name." : environment.strings.Channel_Appearance_StatusFooter, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -1394,7 +1453,279 @@ final class ChannelAppearanceScreenComponent: Component { transition.setFrame(view: emojiStatusSectionView, frame: emojiStatusSectionFrame) } contentHeight += emojiStatusSectionSize.height + contentHeight += sectionSpacing + + var chatPreviewTheme: PresentationTheme = environment.theme + var chatPreviewWallpaper: TelegramWallpaper = presentationData.chatWallpaper + if let updatedWallpaper = self.updatedPeerWallpaper, case .remove = updatedWallpaper { + } else if let temporaryPeerWallpaper = self.temporaryPeerWallpaper { + chatPreviewWallpaper = temporaryPeerWallpaper + } else if let resolvedCurrentTheme = self.resolvedCurrentTheme { + chatPreviewTheme = resolvedCurrentTheme.theme + if let wallpaper = resolvedCurrentTheme.wallpaper { + chatPreviewWallpaper = wallpaper + } + } else if let initialWallpaper = contentsData.peerWallpaper, !initialWallpaper.isEmoticon { + chatPreviewWallpaper = initialWallpaper + } + if !isGroup { + let messageItem = PeerNameColorChatPreviewItem.MessageItem( + outgoing: false, + peerId: EnginePeer.Id(namespace: peer.id.namespace, id: PeerId.Id._internalFromInt64Value(0)), + author: peer.compactDisplayTitle, + photo: peer.profileImageRepresentations, + nameColor: resolvedState.nameColor, + backgroundEmojiId: replyFileId, + reply: (peer.compactDisplayTitle, environment.strings.Channel_Appearance_ExampleReplyText), + linkPreview: (environment.strings.Channel_Appearance_ExampleLinkWebsite, environment.strings.Channel_Appearance_ExampleLinkTitle, environment.strings.Channel_Appearance_ExampleLinkText), + text: environment.strings.Channel_Appearance_ExampleText + ) + + var replyLogoContents: [AnyComponentWithIdentity] = [] + replyLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Channel_Appearance_NameIcon, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + )))) + if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelNameIconLevel { + replyLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( + strings: environment.strings, + level: replyIconLevel + )))) + } + + let replySectionSize = self.replySection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: nil, + footer: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Channel_Appearance_NameColorFooter, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: environment.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + items: [ + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( + itemGenerator: PeerNameColorChatPreviewItem( + context: component.context, + theme: chatPreviewTheme, + componentTheme: chatPreviewTheme, + strings: environment.strings, + sectionId: 0, + fontSize: presentationData.chatFontSize, + chatBubbleCorners: presentationData.chatBubbleCorners, + wallpaper: chatPreviewWallpaper, + dateTimeFormat: environment.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + messageItems: [messageItem] + ), + params: listItemParams + ))), + AnyComponentWithIdentity(id: 1, component: AnyComponent(ListItemComponentAdaptor( + itemGenerator: PeerNameColorItem( + theme: environment.theme, + colors: component.context.peerNameColors, + isProfile: false, + currentColor: resolvedState.nameColor, + updated: { [weak self] value in + guard let self else { + return + } + self.updatedPeerNameColor = value + self.state?.updated(transition: .spring(duration: 0.4)) + }, + sectionId: 0 + ), + params: listItemParams + ))), + AnyComponentWithIdentity(id: 2, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(HStack(replyLogoContents, spacing: 6.0)), + icon: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( + context: component.context, + color: component.context.peerNameColors.get(resolvedState.nameColor, dark: environment.theme.overallDarkAppearance).main, + fileId: replyFileId, + file: replyFileId.flatMap { self.cachedIconFiles[$0] } + ))), + action: { [weak self] view in + guard let self, let resolvedState = self.resolveState(), let view = view as? ListActionItemComponent.View, let iconView = view.iconView else { + return + } + + self.openEmojiSetup(sourceView: iconView, currentFileId: resolvedState.replyFileId, color: component.context.peerNameColors.get(resolvedState.nameColor, dark: environment.theme.overallDarkAppearance).main, subject: .reply) + } + ))) + ] + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + let replySectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: replySectionSize) + if let replySectionView = self.replySection.view { + if replySectionView.superview == nil { + self.scrollView.addSubview(replySectionView) + } + transition.setFrame(view: replySectionView, frame: replySectionFrame) + } + contentHeight += replySectionSize.height + contentHeight += sectionSpacing + } + + if !chatThemes.isEmpty { + var wallpaperLogoContents: [AnyComponentWithIdentity] = [] + wallpaperLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: isGroup ? "Group Wallpaper" : environment.strings.Channel_Appearance_Wallpaper, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + )))) + if let boostLevel = self.boostLevel, boostLevel < (isGroup ? premiumConfiguration.minGroupCustomWallpaperLevel : premiumConfiguration.minChannelCustomWallpaperLevel) { + wallpaperLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( + strings: environment.strings, + level: themeLevel + )))) + } + + var currentTheme = self.currentTheme + var selectedWallpaper: TelegramWallpaper? + if currentTheme == nil, let wallpaper = resolvedState.wallpaper, !wallpaper.isEmoticon { + let theme: PresentationThemeReference = .builtin(.day) + currentTheme = theme + selectedWallpaper = wallpaper + } + + var wallpaperItems: [AnyComponentWithIdentity] = [] + if isGroup { + let incomingMessageItem = PeerNameColorChatPreviewItem.MessageItem( + outgoing: false, + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), + author: peer.compactDisplayTitle, + photo: peer.profileImageRepresentations, + nameColor: .blue, + backgroundEmojiId: nil, + reply: (environment.strings.Appearance_PreviewReplyAuthor, environment.strings.Appearance_PreviewReplyText), + linkPreview: nil, + text: environment.strings.Appearance_PreviewIncomingText + ) + + let outgoingMessageItem = PeerNameColorChatPreviewItem.MessageItem( + outgoing: true, + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), + author: peer.compactDisplayTitle, + photo: peer.profileImageRepresentations, + nameColor: .blue, + backgroundEmojiId: nil, + reply: nil, + linkPreview: nil, + text: environment.strings.Appearance_PreviewOutgoingText + ) + + wallpaperItems.append( + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( + itemGenerator: PeerNameColorChatPreviewItem( + context: component.context, + theme: chatPreviewTheme, + componentTheme: chatPreviewTheme, + strings: environment.strings, + sectionId: 0, + fontSize: presentationData.chatFontSize, + chatBubbleCorners: presentationData.chatBubbleCorners, + wallpaper: chatPreviewWallpaper, + dateTimeFormat: environment.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + messageItems: [incomingMessageItem, outgoingMessageItem] + ), + params: listItemParams + ))) + ) + } + wallpaperItems.append( + AnyComponentWithIdentity(id: 1, component: AnyComponent(ListItemComponentAdaptor( + itemGenerator: ThemeCarouselThemeItem( + context: component.context, + theme: environment.theme, + strings: environment.strings, + sectionId: 0, + themes: chatThemes, + hasNoTheme: true, + animatedEmojiStickers: component.context.animatedEmojiStickers, + themeSpecificAccentColors: [:], + themeSpecificChatWallpapers: [:], + nightMode: environment.theme.overallDarkAppearance, + channelMode: true, + selectedWallpaper: selectedWallpaper, + currentTheme: currentTheme, + updatedTheme: { [weak self] value in + guard let self, value != .builtin(.day) else { + return + } + self.currentTheme = value + self.temporaryPeerWallpaper = nil + if let value { + self.updatedPeerWallpaper = .emoticon(value.emoticon ?? "") + } else { + self.updatedPeerWallpaper = .remove + } + self.state?.updated(transition: .spring(duration: 0.4)) + }, + contextAction: nil + ), + params: listItemParams + ))) + ) + + wallpaperItems.append( + AnyComponentWithIdentity(id: 2, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(HStack(wallpaperLogoContents, spacing: 6.0)), + icon: nil, + action: { [weak self] view in + guard let self else { + return + } + self.openCustomWallpaperSetup() + } + ))) + ) + + let wallpaperSectionSize = self.wallpaperSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: nil, + footer: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: isGroup ? "Set a wallpaper that will be visible for everyone in your group." : environment.strings.Channel_Appearance_WallpaperFooter, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: environment.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + items: wallpaperItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + let wallpaperSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: wallpaperSectionSize) + if let wallpaperSectionView = self.wallpaperSection.view { + if wallpaperSectionView.superview == nil { + self.scrollView.addSubview(wallpaperSectionView) + } + transition.setFrame(view: wallpaperSectionView, frame: wallpaperSectionFrame) + } + contentHeight += wallpaperSectionSize.height + contentHeight += sectionSpacing + } + contentHeight += bottomContentInset var buttonContents: [AnyComponentWithIdentity] = [] @@ -1402,7 +1733,7 @@ final class ChannelAppearanceScreenComponent: Component { Text(text: environment.strings.Channel_Appearance_ApplyButton, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) ))) - let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration) + let requiredLevel = requiredBoostSubject.requiredLevel(group: isGroup, context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(PremiumLockButtonSubtitleComponent( count: Int(requiredLevel), @@ -1472,7 +1803,7 @@ final class ChannelAppearanceScreenComponent: Component { if self.scrollView.scrollIndicatorInsets != scrollInsets { self.scrollView.scrollIndicatorInsets = scrollInsets } - + if !previousBounds.isEmpty, !transition.animation.isImmediate { let bounds = self.scrollView.bounds if bounds.maxY != previousBounds.maxY { @@ -1481,6 +1812,8 @@ final class ChannelAppearanceScreenComponent: Component { } } + self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0)) + self.updateScrolling(transition: transition) return availableSize @@ -1516,8 +1849,9 @@ public class ChannelAppearanceScreen: ViewControllerComponentContainer { ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData) let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.title = presentationData.strings.Channel_Appearance_Title + self.title = "" //presentationData.strings.Channel_Appearance_Title self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) self.ready.set(.never()) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift index 2c7cba50d8..8f5e5fcb86 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift @@ -12,51 +12,7 @@ import PresentationDataUtils import AccountContext import ListItemComponentAdaptor -private enum PeerNameColorEntryId: Hashable { - case color(Int32) -} - -private enum PeerNameColorEntry: Comparable, Identifiable { - case color(Int, PeerNameColor, PeerNameColors.Colors, Bool, Bool) - - var stableId: PeerNameColorEntryId { - switch self { - case let .color(_, color, _, _, _): - return .color(color.rawValue) - } - } - - static func ==(lhs: PeerNameColorEntry, rhs: PeerNameColorEntry) -> Bool { - switch lhs { - case let .color(lhsIndex, lhsColor, lhsAccentColor, lhsIsDark, lhsSelected): - if case let .color(rhsIndex, rhsColor, rhsAccentColor, rhsIsDark, rhsSelected) = rhs, lhsIndex == rhsIndex, lhsColor == rhsColor, lhsAccentColor == rhsAccentColor, lhsIsDark == rhsIsDark, lhsSelected == rhsSelected { - return true - } else { - return false - } - } - } - - static func <(lhs: PeerNameColorEntry, rhs: PeerNameColorEntry) -> Bool { - switch lhs { - case let .color(lhsIndex, _, _, _, _): - switch rhs { - case let .color(rhsIndex, _, _, _, _): - return lhsIndex < rhsIndex - } - } - } - - func item(action: @escaping (PeerNameColor) -> Void) -> ListViewItem { - switch self { - case let .color(_, index, colors, isDark, selected): - return PeerNameColorIconItem(index: index, colors: colors, isDark: isDark, selected: selected, action: action) - } - } -} - - -private class PeerNameColorIconItem: ListViewItem { +private class PeerNameColorIconItem { let index: PeerNameColor let colors: PeerNameColors.Colors let isDark: Bool @@ -70,55 +26,10 @@ private class PeerNameColorIconItem: ListViewItem { self.selected = selected 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 = PeerNameColorIconItemNode() - 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 PeerNameColorIconItemNode) - if let nodeValue = node() as? PeerNameColorIconItemNode { - let layout = nodeValue.asyncLayout() - async { - let (nodeLayout, apply) = layout(self, params) - Queue.mainQueue().async { - completion(nodeLayout, { _ in - let animated: Bool - if case .Crossfade = animation { - animated = true - } else { - animated = false - } - apply(animated) - }) - } - } - } - } - } - - public var selectable = true - public func selected(listView: ListView) { - self.action(self.index) - } } -private func generateRingImage(nameColor: PeerNameColors.Colors) -> UIImage? { - return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in +private func generateRingImage(nameColor: PeerNameColors.Colors, size: CGSize = CGSize(width: 40.0, height: 40.0)) -> UIImage? { + return generateImage(size, rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -211,14 +122,14 @@ public func generateSettingsMenuPeerColorsLabelIcon(colors: [PeerNameColors.Colo })! } -private final class PeerNameColorIconItemNode : ListViewItemNode { +private final class PeerNameColorIconItemNode : ASDisplayNode { private let containerNode: ContextControllerSourceNode private let fillNode: ASImageNode private let ringNode: ASImageNode var item: PeerNameColorIconItem? - init() { + override init() { self.containerNode = ContextControllerSourceNode() self.fillNode = ASImageNode() @@ -229,7 +140,7 @@ private final class PeerNameColorIconItemNode : ListViewItemNode { self.ringNode.displaysAsynchronously = false self.ringNode.displayWithoutProcessing = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init() self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.ringNode) @@ -239,13 +150,20 @@ private final class PeerNameColorIconItemNode : ListViewItemNode { override func didLoad() { super.didLoad() - self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapped))) + } + + @objc private func tapped() { + guard let item = self.item else { + return + } + item.action(item.index) } func setSelected(_ selected: Bool, animated: Bool = false) { let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate if selected { - transition.updateTransformScale(node: self.fillNode, scale: 0.8) + transition.updateTransformScale(node: self.fillNode, scale: 0.75) transition.updateTransformScale(node: self.ringNode, scale: 1.0) } else { transition.updateTransformScale(node: self.fillNode, scale: 1.0) @@ -253,64 +171,39 @@ private final class PeerNameColorIconItemNode : ListViewItemNode { } } - func asyncLayout() -> (PeerNameColorIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { + func updateItem(_ item: PeerNameColorIconItem, size: CGSize) { let currentItem = self.item - - return { [weak self] item, params in - var updatedAccentColor = false - var updatedSelected = false - - if currentItem == nil || currentItem?.colors != item.colors { - updatedAccentColor = true - } - if currentItem?.selected != item.selected { - updatedSelected = true - } - - let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 60.0, height: 56.0), insets: UIEdgeInsets()) - return (itemLayout, { animated in - if let strongSelf = self { - strongSelf.item = item - - if updatedAccentColor { - strongSelf.fillNode.image = generatePeerNameColorImage(nameColor: item.colors, isDark: item.isDark) - strongSelf.ringNode.image = generateRingImage(nameColor: item.colors) - } - - let center = CGPoint(x: 30.0, y: 28.0) - let bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 40.0, height: 40.0)) - strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.contentSize) - - strongSelf.fillNode.position = center - strongSelf.ringNode.position = center - - strongSelf.fillNode.bounds = bounds - strongSelf.ringNode.bounds = bounds - - if updatedSelected { - strongSelf.setSelected(item.selected, animated: !updatedAccentColor && currentItem != nil) - } - } - }) + + var updatedAccentColor = false + var updatedSelected = false + + if currentItem == nil || currentItem?.colors != item.colors { + updatedAccentColor = true + } + if currentItem?.selected != item.selected { + updatedSelected = true } - } - - 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.item = item - 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) + if updatedAccentColor { + self.fillNode.image = generatePeerNameColorImage(nameColor: item.colors, isDark: item.isDark, bounds: size, size: size) + self.ringNode.image = generateRingImage(nameColor: item.colors, size: size) + } - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0) + let bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) + self.containerNode.frame = CGRect(origin: CGPoint(), size: bounds.size) + + self.fillNode.position = center + self.ringNode.position = center + + self.fillNode.bounds = bounds + self.ringNode.bounds = bounds + + if updatedSelected { + self.setSelected(item.selected, animated: !updatedAccentColor && currentItem != nil) + } } } @@ -389,40 +282,6 @@ final class PeerNameColorItem: ListViewItem, ItemListItem, ListItemComponentAdap } } -private struct PeerNameColorItemNodeTransition { - let deletions: [ListViewDeleteItem] - let insertions: [ListViewInsertItem] - let updates: [ListViewUpdateItem] - let updatePosition: Bool -} - -private func preparedTransition(action: @escaping (PeerNameColor) -> Void, from fromEntries: [PeerNameColorEntry], to toEntries: [PeerNameColorEntry], updatePosition: Bool) -> PeerNameColorItemNodeTransition { - 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(action: action), directionHint: .Down) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(action: action), directionHint: nil) } - - return PeerNameColorItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, updatePosition: updatePosition) -} - -private func ensureColorVisible(listNode: ListView, color: PeerNameColor, animated: Bool) -> Bool { - var resultNode: PeerNameColorIconItemNode? - listNode.forEachItemNode { node in - if resultNode == nil, let node = node as? PeerNameColorIconItemNode { - if node.item?.index == color { - resultNode = node - } - } - } - if let resultNode = resultNode { - listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 76.0) - return true - } else { - return false - } -} - final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { private let containerNode: ASDisplayNode private let backgroundNode: ASDisplayNode @@ -430,16 +289,13 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { private let bottomStripeNode: ASDisplayNode private let maskNode: ASImageNode - private let listNode: ListView - private var entries: [PeerNameColorEntry]? - private var enqueuedTransitions: [PeerNameColorItemNodeTransition] = [] + private var items: [PeerNameColorIconItem] = [] + private var itemNodes: [Int32 : PeerNameColorIconItemNode] = [:] private var initialized = false private var item: PeerNameColorItem? private var layoutParams: ListViewItemLayoutParams? - - private var tapping = false - + var tag: ItemListItemTag? { return self.item?.tag } @@ -458,53 +314,9 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { self.maskNode = ASImageNode() - self.listNode = ListView() - self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) self.addSubnode(self.containerNode) - self.addSubnode(self.listNode) - } - - override func didLoad() { - super.didLoad() - self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true - } - - private func enqueueTransition(_ transition: PeerNameColorItemNodeTransition) { - self.enqueuedTransitions.append(transition) - - if let _ = self.item { - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } - } - } - - private func dequeueTransition() { - guard let item = self.item, let transition = self.enqueuedTransitions.first else { - return - } - self.enqueuedTransitions.remove(at: 0) - - let options = ListViewDeleteAndInsertOptions() - var scrollToItem: ListViewScrollToItem? - if !self.initialized || transition.updatePosition || !self.tapping { - let displayOrder: [Int32] - if item.isProfile { - displayOrder = item.colors.profileDisplayOrder - } else { - displayOrder = item.colors.displayOrder - } - if let index = displayOrder.firstIndex(where: { $0 == item.currentColor?.rawValue }) { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-70.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 asyncLayout() -> (_ item: PeerNameColorItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { @@ -520,7 +332,19 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { let insets: UIEdgeInsets let separatorHeight = UIScreenPixel - contentSize = CGSize(width: params.width, height: 60.0) + let itemsPerRow: Int + let displayOrder: [Int32] + if item.isProfile { + displayOrder = item.colors.profileDisplayOrder + itemsPerRow = 8 + } else { + displayOrder = item.colors.displayOrder + itemsPerRow = 7 + } + + let rowsCount = ceil(CGFloat(displayOrder.count) / CGFloat(itemsPerRow)) + + contentSize = CGSize(width: params.width, height: 48.0 * rowsCount) insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -595,22 +419,11 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.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)) - var listInsets = UIEdgeInsets() - listInsets.top += params.leftInset + 8.0 - listInsets.bottom += params.rightInset + 8.0 - - strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width) - strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0) - strongSelf.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 }) - - var entries: [PeerNameColorEntry] = [] - - let displayOrder: [Int32] - if item.isProfile { - displayOrder = item.colors.profileDisplayOrder - } else { - displayOrder = item.colors.displayOrder + let action: (PeerNameColor) -> Void = { color in + item.updated(color) } + + var items: [PeerNameColorIconItem] = [] var i: Int = 0 for index in displayOrder { let color = PeerNameColor(rawValue: index) @@ -620,29 +433,42 @@ final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { } else { colors = item.colors.get(color, dark: item.theme.overallDarkAppearance) } - entries.append(.color(i, color, colors, item.theme.overallDarkAppearance, color == item.currentColor)) + items.append(PeerNameColorIconItem(index: color, colors: colors, isDark: item.theme.overallDarkAppearance, selected: color == item.currentColor, action: action)) i += 1 } + strongSelf.items = items - let action: (PeerNameColor) -> Void = { [weak self] color in - guard let self else { - return + let sideInset: CGFloat = 10.0 + let iconSize = CGSize(width: 32.0, height: 32.0) + + let spacing = (params.width - sideInset * 2.0 - iconSize.width * CGFloat(itemsPerRow)) / CGFloat(itemsPerRow - 1) + + var origin = CGPoint(x: sideInset, y: 10.0) + + i = 0 + for item in items { + let iconItemNode: PeerNameColorIconItemNode + if let current = strongSelf.itemNodes[item.index.rawValue] { + iconItemNode = current + } else { + iconItemNode = PeerNameColorIconItemNode() + strongSelf.itemNodes[item.index.rawValue] = iconItemNode + strongSelf.containerNode.addSubnode(iconItemNode) } - self.tapping = true - item.updated(color) - Queue.mainQueue().after(0.4) { - self.tapping = false + + let itemFrame = CGRect(origin: origin, size: iconSize) + origin.x += iconSize.width + spacing + iconItemNode.frame = itemFrame + iconItemNode.updateItem(item, size: iconSize) + + i += 1 + if i == itemsPerRow { + i = 0 + origin.x = sideInset + origin.y += iconSize.height + 10.0 } - let _ = ensureColorVisible(listNode: self.listNode, color: color, animated: true) } - - let previousEntries = strongSelf.entries ?? [] - let updatePosition = currentItem != nil && previousEntries.count != entries.count - let transition = preparedTransition(action: action, from: previousEntries, to: entries, updatePosition: updatePosition) - strongSelf.enqueueTransition(transition) - - strongSelf.entries = entries } }) } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift index e9101135d2..8ae84d1763 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift @@ -23,17 +23,19 @@ final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem, ListIte let theme: PresentationTheme let componentTheme: PresentationTheme let strings: PresentationStrings + let topInset: CGFloat let sectionId: ItemListSectionId let peer: EnginePeer? let subtitleString: String? let files: [Int64: TelegramMediaFile] let nameDisplayOrder: PresentationPersonNameOrder - init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, peer: EnginePeer?, subtitleString: String? = nil, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder) { + init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, topInset: CGFloat, sectionId: ItemListSectionId, peer: EnginePeer?, subtitleString: String? = nil, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder) { self.context = context self.theme = theme self.componentTheme = componentTheme self.strings = strings + self.topInset = topInset self.sectionId = sectionId self.peer = peer self.subtitleString = subtitleString @@ -143,7 +145,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode { return { [weak self] item, params, neighbors in let separatorHeight = UIScreenPixel - let contentSize = CGSize(width: params.width, height: 210.0) + let contentSize = CGSize(width: params.width, height: 210.0 + item.topInset) var insets = itemListNeighborsGroupedInsets(neighbors, params) if params.width <= 320.0 { insets.top = 0.0 @@ -215,7 +217,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode { let coverFrame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0) let avatarSize: CGFloat = 104.0 - let avatarFrame = CGRect(origin: CGPoint(x: floor((coverFrame.width - avatarSize) * 0.5), y: coverFrame.minY + 24.0), size: CGSize(width: avatarSize, height: avatarSize)) + let avatarFrame = CGRect(origin: CGPoint(x: floor((coverFrame.width - avatarSize) * 0.5), y: coverFrame.minY + item.topInset + 24.0), size: CGSize(width: avatarSize, height: avatarSize)) let _ = self.background.update( transition: .immediate, @@ -259,7 +261,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode { if self.avatarNode.supernode == nil { self.addSubnode(self.avatarNode) } - self.avatarNode.frame = avatarFrame.offsetBy(dx: coverFrame.minX, dy: coverFrame.minY) + self.avatarNode.frame = avatarFrame.offsetBy(dx: coverFrame.minX, dy: 0.0) let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift index 798bcdc873..8f447a4472 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift @@ -201,6 +201,7 @@ private enum PeerNameColorScreenEntry: ItemListNodeEntry { theme: presentationData.theme, componentTheme: presentationData.theme, strings: presentationData.strings, + topInset: 0.0, sectionId: self.section, peer: peer, files: files, diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreenComponent.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreenComponent.swift deleted file mode 100644 index 997b96aaa8..0000000000 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreenComponent.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import SwiftSignalKit -import Postbox -import TelegramCore -import TelegramPresentationData -import TelegramUIPreferences -import PresentationDataUtils -import AccountContext -import UndoUI - diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 89dee442cb..3e186da46a 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -928,6 +928,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, + mediaDraftContentType: nil, inputActivities: hasInputActivity ? [(author, .typingText)] : [], promoInfo: nil, ignoreUnreadBadge: false, diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 7eb14f88d4..6906d2abfd 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -1030,10 +1030,22 @@ final class ShareWithPeersScreenComponent: Component { if case .user = peer { subtitle = environment.strings.VoiceChat_PersonalAccount } else { - if let count = component.stateContext.stateValue?.participants[peer.id] { - subtitle = environment.strings.Conversation_StatusSubscribers(Int32(count)) + if case let .channel(channel) = peer { + if case .broadcast = channel.info { + if let count = component.stateContext.stateValue?.participants[peer.id] { + subtitle = environment.strings.Conversation_StatusSubscribers(Int32(count)) + } else { + subtitle = environment.strings.Channel_Status + } + } else { + if let count = component.stateContext.stateValue?.participants[peer.id] { + subtitle = environment.strings.Conversation_StatusMembers(Int32(count)) + } else { + subtitle = environment.strings.Group_Status + } + } } else { - subtitle = environment.strings.Channel_Status + subtitle = nil } } @@ -1449,11 +1461,24 @@ final class ShareWithPeersScreenComponent: Component { } if case .channels = component.stateContext.subject { if case let .channel(channel) = peer, channel.addressName == nil, index == nil { + let title: String + let text: String + + switch channel.info { + case .broadcast: + title = environment.strings.BoostGift_Channels_PrivateChannel_Title + text = environment.strings.BoostGift_Channels_PrivateChannel_Text + case .group: + //TODO:localize + title = "Group is Private" + text = "Are you sure you want to add a private group? Users won't be able to join it without an invite link." + } + let alertController = textAlertController( context: component.context, forceTheme: environment.theme, - title: environment.strings.BoostGift_Channels_PrivateChannel_Title, - text: environment.strings.BoostGift_Channels_PrivateChannel_Text, + title: title, + text: text, actions: [ TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.BoostGift_Channels_PrivateChannel_Add, action: { @@ -2370,9 +2395,10 @@ final class ShareWithPeersScreenComponent: Component { subtitle = environment.strings.BoostGift_Subscribers_Subtitle("\(10)").string actionButtonTitle = environment.strings.BoostGift_Subscribers_Save case .channels: - title = environment.strings.BoostGift_Channels_Title - subtitle = environment.strings.BoostGift_Channels_Subtitle("\(component.context.userLimits.maxGiveawayChannelsCount)").string - actionButtonTitle = environment.strings.BoostGift_Channels_Save + //TODO:localize + title = "Add Groups or Channels"// environment.strings.BoostGift_Channels_Title + subtitle = "select up to \(component.context.userLimits.maxGiveawayChannelsCount) groups or channels" //environment.strings.BoostGift_Channels_Subtitle("\(component.context.userLimits.maxGiveawayChannelsCount)").string + actionButtonTitle = "Save Groups and Channels" // environment.strings.BoostGift_Channels_Save } let titleComponent: AnyComponent diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift index 89d1bdf7a2..715bad782b 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift @@ -640,7 +640,7 @@ public extension ShareWithPeersScreen { } for item in searchResults { - if let peer = item.peer, case let .channel(channel) = peer, case .broadcast = channel.info { + if let peer = item.peer, case .channel = peer { selectedPeers.append(peer) existingIds.insert(peer.id) } @@ -672,7 +672,7 @@ public extension ShareWithPeersScreen { if self.initialPeerIds.contains(peer.id) { return false } - if case let .channel(channel) = peer, case .broadcast = channel.info { + if case .channel = peer { return true } return false diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 1832753fc5..2b5467ff88 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -6100,7 +6100,7 @@ public final class StoryItemSetContainerComponent: Component { return .single(items) } - + private func performMyMoreAction(sourceView: UIView, gesture: ContextGesture?) { guard let component = self.component, let controller = component.controller() else { return @@ -6111,7 +6111,6 @@ public final class StoryItemSetContainerComponent: Component { let baseRatePromise = ValuePromise(component.storyItemSharedState.baseRate) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - let contextItems = baseRatePromise.get() |> mapToSignal { [weak self, weak component] baseRate -> Signal in guard let self, let component else { @@ -6194,7 +6193,7 @@ public final class StoryItemSetContainerComponent: Component { }))) items.append(.separator) - + items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromProfile : component.strings.Story_Context_SaveToProfile, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Stories/Context Menu/Unpin" : "Stories/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in @@ -6328,14 +6327,14 @@ public final class StoryItemSetContainerComponent: Component { let baseRatePromise = ValuePromise(component.storyItemSharedState.baseRate) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - + let contextItems = baseRatePromise.get() |> mapToSignal { [weak self, weak component] baseRate -> Signal in guard let self, let component else { return .complete() } + var items: [ContextMenuItem] = [] - if case .file = component.slice.item.storyItem.media { var speedValue: String = presentationData.strings.PlaybackSpeed_Normal var speedIconText: String = "1x" @@ -6463,7 +6462,7 @@ public final class StoryItemSetContainerComponent: Component { } let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) - |> deliverOnMainQueue).startStandalone(next: { [weak self] link in + |> deliverOnMainQueue).startStandalone(next: { [weak self] link in guard let self, let component = self.component else { return } @@ -6990,7 +6989,7 @@ public final class StoryItemSetContainerComponent: Component { let (tip, tipSignal) = self.getLinkedStickerPacks() - let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal) + let contextItems = ContextController.Items(id: 0, content: .list(items), tip: tip, tipSignal: tipSignal) let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: .single(contextItems), gesture: gesture) contextController.dismissed = { [weak self] in diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 5b9643489f..d374b63b52 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -50,6 +50,7 @@ import ObjectiveC import LocationUI import ReactionSelectionNode import StoryQualityUpgradeSheetScreen +import AudioWaveform private var ObjCKey_DeinitWatcher: Int? diff --git a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift index f83e5aaeba..1419496161 100644 --- a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift +++ b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift @@ -292,6 +292,7 @@ public class ImmediateTextNodeWithEntities: TextNode { public var displaySpoilers = false public var displaySpoilerEffect = true public var spoilerColor: UIColor = .black + public var balancedTextLayout: Bool = false private var enableLooping: Bool = true diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index cbb09465a8..dc23023e42 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -792,6 +792,10 @@ public class VideoMessageCameraScreen: ViewController { func animateIn() { self.animatingIn = true +// if let chatNode = self.controller?.chatNode { +// chatNode.supernode?.view.insertSubview(self.backgroundView, aboveSubview: chatNode.view) +// } + self.backgroundView.alpha = 0.0 UIView.animate(withDuration: 0.4, animations: { self.backgroundView.alpha = 1.0 @@ -817,6 +821,7 @@ public class VideoMessageCameraScreen: ViewController { UIView.animate(withDuration: 0.25, animations: { self.backgroundView.alpha = 0.0 }, completion: { _ in + self.backgroundView.removeFromSuperview() completion() }) @@ -1401,19 +1406,23 @@ public class VideoMessageCameraScreen: ViewController { } } + fileprivate weak var chatNode: ASDisplayNode? + public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, allowLiveUpload: Bool, viewOnceAvailable: Bool, inputPanelFrame: CGRect, + chatNode: ASDisplayNode?, completion: @escaping (EnqueueMessage?, Bool?, Int32?) -> Void ) { self.context = context self.updatedPresentationData = updatedPresentationData - self.inputPanelFrame = inputPanelFrame self.allowLiveUpload = allowLiveUpload self.viewOnceAvailable = viewOnceAvailable + self.inputPanelFrame = inputPanelFrame + self.chatNode = chatNode self.completion = completion self.recordingStatus = RecordingStatus(micLevel: self.micLevelValue.get(), duration: self.durationValue.get()) diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Boost.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Boost.imageset/Contents.json new file mode 100644 index 0000000000..7781121b21 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Boost.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "boost_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Boost.imageset/boost_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Boost.imageset/boost_24.pdf new file mode 100644 index 0000000000..717e218c2f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Boost.imageset/boost_24.pdf @@ -0,0 +1,295 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 24.000000 24.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +1.000000 0.000000 -0.000000 1.000000 5.944336 -2.413574 cm +8.262308 16.282097 m +7.803495 16.282097 7.453221 16.692017 7.524779 17.145216 c +8.522446 23.463770 l +8.646609 24.250139 7.618237 24.658890 7.168687 24.001854 c +0.131716 13.717052 l +-0.207343 13.221502 0.147505 12.548755 0.747946 12.548755 c +3.842392 12.548755 l +4.301206 12.548755 4.651480 12.138834 4.579922 11.685636 c +3.582255 5.367081 l +3.458092 4.580711 4.486464 4.171961 4.936014 4.828997 c +11.972985 15.113801 l +12.312045 15.609349 11.957196 16.282097 11.356755 16.282097 c +8.262308 16.282097 l +h +0.000000 0.000000 0.000000 scn +f* +n + +endstream +endobj + +2 0 obj + 627 +endobj + +3 0 obj + << /Length 4 0 R + /FunctionType 4 + /Domain [ 0.000000 1.000000 ] + /Range [ 0.000000 1.000000 ] + >> +stream +{ 0 gt { 0 } { 1 } ifelse } +endstream +endobj + +4 0 obj + 27 +endobj + +5 0 obj + << /Type /XObject + /Length 6 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << /ExtGState << /E1 << /SMask << /Type /Mask + /G 1 0 R + /S /Alpha + /TR 3 0 R + >> + /Type /ExtGState + >> >> >> + /BBox [ 0.000000 0.000000 24.000000 24.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +q +1.000000 0.000000 -0.000000 1.000000 5.944336 -2.413574 cm +0.000000 0.000000 0.000000 scn +11.972985 15.113801 m +10.875327 15.864830 l +11.972985 15.113801 l +h +0.131716 13.717052 m +-0.965942 14.468081 l +0.131716 13.717052 l +h +8.522446 23.463770 m +7.208721 23.671200 l +8.522446 23.463770 l +h +7.524779 17.145216 m +6.211054 17.352646 l +7.524779 17.145216 l +h +8.838504 16.937786 m +9.836171 23.256340 l +7.208721 23.671200 l +6.211054 17.352646 l +8.838504 16.937786 l +h +6.071028 24.752884 m +-0.965942 14.468081 l +1.229375 12.966022 l +8.266346 23.250824 l +6.071028 24.752884 l +h +0.747946 11.218755 m +3.842392 11.218755 l +3.842392 13.878755 l +0.747946 13.878755 l +0.747946 11.218755 l +h +3.266197 11.893065 m +2.268530 5.574511 l +4.895980 5.159651 l +5.893647 11.478206 l +3.266197 11.893065 l +h +6.033673 4.077967 m +13.070644 14.362771 l +10.875327 15.864830 l +3.838356 5.580027 l +6.033673 4.077967 l +h +11.356755 17.612097 m +8.262308 17.612097 l +8.262308 14.952097 l +11.356755 14.952097 l +11.356755 17.612097 l +h +13.070644 14.362771 m +14.013656 15.741019 13.026727 17.612097 11.356755 17.612097 c +11.356755 14.952097 l +10.887666 14.952097 10.610434 15.477679 10.875327 15.864830 c +13.070644 14.362771 l +h +2.268530 5.574511 m +1.923201 3.387428 4.783359 2.250586 6.033673 4.077967 c +3.838356 5.580027 l +3.905082 5.677549 4.024478 5.770592 4.176677 5.810925 c +4.314623 5.847483 4.439354 5.830826 4.535248 5.792711 c +4.631143 5.754595 4.733274 5.681080 4.808484 5.559799 c +4.891464 5.425989 4.914409 5.276369 4.895980 5.159651 c +2.268530 5.574511 l +h +3.842392 11.218755 m +3.483944 11.218755 3.210293 11.539005 3.266197 11.893065 c +5.893647 11.478206 l +6.092667 12.738664 5.118467 13.878755 3.842392 13.878755 c +3.842392 11.218755 l +h +-0.965942 14.468081 m +-1.908953 13.089835 -0.922029 11.218755 0.747946 11.218755 c +0.747946 13.878755 l +1.217038 13.878755 1.494266 13.353170 1.229375 12.966022 c +-0.965942 14.468081 l +h +9.836171 23.256340 m +10.181499 25.443424 7.321342 26.580265 6.071028 24.752884 c +8.266346 23.250824 l +8.199619 23.153301 8.080223 23.060259 7.928024 23.019926 c +7.790078 22.983368 7.665347 23.000025 7.569453 23.038141 c +7.473559 23.076256 7.371428 23.149771 7.296218 23.271051 c +7.213237 23.404861 7.190292 23.554482 7.208721 23.671200 c +9.836171 23.256340 l +h +6.211054 17.352646 m +6.012034 16.092186 6.986233 14.952097 8.262308 14.952097 c +8.262308 17.612097 l +8.620757 17.612097 8.894408 17.291845 8.838504 16.937786 c +6.211054 17.352646 l +h +f +n +Q +Q + +endstream +endobj + +6 0 obj + 2492 +endobj + +7 0 obj + << /Type /XObject + /Length 8 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 24.000000 24.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 24.000000 m +24.000000 24.000000 l +24.000000 0.000000 l +0.000000 0.000000 l +0.000000 24.000000 l +h +f +n +Q + +endstream +endobj + +8 0 obj + 232 +endobj + +9 0 obj + << /XObject << /X1 5 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 7 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +10 0 obj + << /Length 11 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +11 0 obj + 46 +endobj + +12 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 9 0 R + /Contents 10 0 R + /Parent 13 0 R + >> +endobj + +13 0 obj + << /Kids [ 12 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +14 0 obj + << /Pages 13 0 R + /Type /Catalog + >> +endobj + +xref +0 15 +0000000000 65535 f +0000000010 00000 n +0000000885 00000 n +0000000907 00000 n +0000001082 00000 n +0000001103 00000 n +0000004215 00000 n +0000004238 00000 n +0000004718 00000 n +0000004740 00000 n +0000005038 00000 n +0000005142 00000 n +0000005164 00000 n +0000005340 00000 n +0000005416 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 14 0 R + /Size 15 +>> +startxref +5477 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/Boost.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/Boost.imageset/Contents.json new file mode 100644 index 0000000000..d54f590b38 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/Boost.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "boost_14.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/Boost.imageset/boost_14.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/Boost.imageset/boost_14.pdf new file mode 100644 index 0000000000..9212a3750c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/Boost.imageset/boost_14.pdf @@ -0,0 +1,79 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.479492 0.860352 cm +0.000000 0.000000 0.000000 scn +4.795081 7.219173 m +4.528805 7.219173 4.325521 7.457074 4.367050 7.720091 c +4.946062 11.387165 l +5.018121 11.843539 4.421298 12.080759 4.160398 11.699444 c +0.076443 5.730586 l +-0.120333 5.442991 0.085605 5.052556 0.434075 5.052556 c +2.230532 5.052556 l +2.496808 5.052556 2.700092 4.814656 2.658563 4.551639 c +2.079551 0.884564 l +2.007492 0.428190 2.604315 0.190969 2.865215 0.572285 c +6.949171 6.541143 l +7.145946 6.828738 6.940008 7.219173 6.591538 7.219173 c +4.795081 7.219173 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 609 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 10.000000 14.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000699 00000 n +0000000721 00000 n +0000000894 00000 n +0000000968 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1027 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/Boosts.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/Boosts.imageset/Contents.json new file mode 100644 index 0000000000..c6b6dfdf00 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/Boosts.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "boosts_14.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/Boosts.imageset/boosts_14.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/Boosts.imageset/boosts_14.pdf new file mode 100644 index 0000000000..508bb91b58 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/Boosts.imageset/boosts_14.pdf @@ -0,0 +1,99 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.479492 0.860352 cm +0.000000 0.000000 0.000000 scn +4.795080 7.219173 m +4.528805 7.219173 4.325521 7.457073 4.367050 7.720090 c +4.946061 11.387165 l +5.018120 11.843539 4.421297 12.080759 4.160397 11.699444 c +0.076443 5.730586 l +-0.120333 5.442991 0.085605 5.052556 0.434075 5.052556 c +2.230532 5.052556 l +2.496808 5.052556 2.700092 4.814656 2.658562 4.551639 c +2.079551 0.884564 l +2.007492 0.428190 2.604315 0.190969 2.865215 0.572285 c +6.949170 6.541143 l +7.145946 6.828738 6.940008 7.219173 6.591537 7.219173 c +4.795080 7.219173 l +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 7.258789 0.860352 cm +0.000000 0.000000 0.000000 scn +0.837258 4.283866 m +1.995213 5.976261 l +2.646084 6.927535 1.964908 8.218975 0.812273 8.218975 c +0.000000 8.218975 l +2.381373 11.699444 l +2.642273 12.080759 3.239096 11.843539 3.167037 11.387165 c +2.588026 7.720091 l +2.546496 7.457074 2.749780 7.219173 3.016056 7.219173 c +4.812513 7.219173 l +5.160984 7.219173 5.366921 6.828738 5.170146 6.541143 c +1.086190 0.572285 l +0.825291 0.190969 0.228467 0.428190 0.300527 0.884564 c +0.837258 4.283866 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1153 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 14.000000 14.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001243 00000 n +0000001266 00000 n +0000001439 00000 n +0000001513 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1572 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostLarge.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/BoostLarge.imageset/Contents.json new file mode 100644 index 0000000000..23d4f8e500 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostLarge.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "boosts.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostLarge.imageset/boosts.pdf b/submodules/TelegramUI/Images.xcassets/Premium/BoostLarge.imageset/boosts.pdf new file mode 100644 index 0000000000..f9c4e98c98 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostLarge.imageset/boosts.pdf @@ -0,0 +1,100 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 28.934570 23.770996 cm +0.000000 0.000000 0.000000 scn +19.893095 16.420483 m +19.893103 16.420439 19.893110 16.420393 19.893118 16.420349 c +24.322737 22.894407 l +25.011452 23.900990 24.290668 25.267513 23.071022 25.267513 c +16.783052 25.267513 l +16.020958 25.267513 15.406286 25.824291 15.287010 26.530622 c +15.286899 26.530460 l +15.260291 26.687939 15.258309 26.852856 15.284810 27.020691 c +17.311213 39.854584 l +17.563419 41.451893 15.474540 42.282173 14.561391 40.947567 c +0.267549 20.056564 l +-0.421166 19.049982 0.299617 17.683458 1.519263 17.683458 c +7.807235 17.683458 l +8.739199 17.683458 9.450692 16.850807 9.305341 15.930248 c +7.278937 3.096359 l +7.026731 1.499050 9.115610 0.668770 10.028760 2.003372 c +19.893095 16.420483 l +h +19.104115 28.767513 m +19.790094 33.112053 l +25.151236 40.947567 l +26.064384 42.282173 28.153263 41.451893 27.901056 39.854580 c +26.002724 27.831802 l +25.198483 28.413969 24.199286 28.767513 23.071022 28.767513 c +19.104115 28.767513 l +h +27.211313 20.918013 m +28.156090 22.298841 28.298967 23.884453 27.854807 25.267483 c +33.660732 25.267483 l +34.880375 25.267483 35.601158 23.900959 34.912445 22.894377 c +20.618603 2.003372 l +19.705454 0.668770 17.616575 1.499050 17.868782 3.096359 c +18.724150 8.513695 l +27.211313 20.918013 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1339 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 90.000000 90.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001429 00000 n +0000001452 00000 n +0000001625 00000 n +0000001699 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1758 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/Contents.json new file mode 100644 index 0000000000..62f1f00fa3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "voicetotext_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/voicetotext_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/voicetotext_30.pdf new file mode 100644 index 0000000000..2c905d2cdb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/AudioTranscription.imageset/voicetotext_30.pdf @@ -0,0 +1,140 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.834961 2.334961 cm +0.000000 0.000000 0.000000 scn +9.164956 24.330078 m +6.312406 24.330078 3.999956 22.017628 3.999956 19.165077 c +3.999956 13.165078 l +3.999956 10.312528 6.312406 8.000078 9.164956 8.000078 c +12.017498 8.000078 14.329942 10.312513 14.329956 13.165051 c +14.329956 13.165078 l +14.330078 16.165051 l +14.330093 16.532320 14.032374 16.830063 13.665105 16.830078 c +13.297836 16.830093 13.000093 16.532375 13.000078 16.165104 c +12.999956 13.165105 l +12.999956 13.165078 l +12.999956 11.047067 11.282969 9.330078 9.164956 9.330078 c +7.046944 9.330078 5.329956 11.047067 5.329956 13.165078 c +5.329956 19.165077 l +5.329956 21.283091 7.046944 23.000078 9.164956 23.000078 c +9.458838 23.000078 9.744217 22.967144 10.017904 22.905033 c +10.376066 22.823750 10.732306 23.048206 10.813588 23.406368 c +10.894870 23.764530 10.670414 24.120770 10.312252 24.202051 c +9.942642 24.285933 9.558549 24.330078 9.164956 24.330078 c +h +1.330000 13.665080 m +1.330000 14.032350 1.032269 14.330080 0.665000 14.330080 c +0.297731 14.330080 0.000000 14.032350 0.000000 13.665080 c +0.000000 13.165079 l +0.000000 8.327017 3.748748 4.364523 8.500000 4.023836 c +8.500000 0.665079 l +8.500000 0.297810 8.797730 0.000080 9.165000 0.000080 c +9.532269 0.000080 9.830000 0.297810 9.830000 0.665079 c +9.830000 4.023836 l +14.581252 4.364523 18.330002 8.327017 18.330002 13.165079 c +18.330002 13.665080 l +18.330002 14.032350 18.032270 14.330080 17.665001 14.330080 c +17.297731 14.330080 17.000000 14.032350 17.000000 13.665080 c +17.000000 13.165079 l +17.000000 8.837929 13.492150 5.330080 9.165000 5.330080 c +4.837850 5.330080 1.330000 8.837929 1.330000 13.165079 c +1.330000 13.665080 l +h +20.773884 23.920847 m +20.670631 24.168657 20.428501 24.330078 20.160038 24.330078 c +19.891579 24.330078 19.649448 24.168657 19.546192 23.920847 c +17.046192 17.920847 l +16.904936 17.581829 17.065252 17.192490 17.404270 17.051231 c +17.743288 16.909975 18.132627 17.070292 18.273884 17.409309 c +18.936707 19.000078 l +21.383373 19.000078 l +22.046192 17.409309 l +22.187450 17.070292 22.576790 16.909975 22.915808 17.051231 c +23.254826 17.192490 23.415142 17.581829 23.273886 17.920847 c +20.773884 23.920847 l +h +20.160038 21.936077 m +20.829205 20.330078 l +19.490871 20.330078 l +20.160038 21.936077 l +h +13.189813 23.135303 m +13.449512 23.395002 13.870566 23.395002 14.130264 23.135303 c +16.130264 21.135303 l +16.194023 21.071547 16.242128 20.998062 16.274580 20.919630 c +16.306854 20.841801 16.324774 20.756514 16.325035 20.667072 c +16.325039 20.665077 l +16.325035 20.663084 l +16.324532 20.493561 16.259607 20.324194 16.130264 20.194851 c +14.130264 18.194853 l +13.870566 17.935154 13.449512 17.935154 13.189813 18.194853 c +12.930115 18.454552 12.930115 18.875607 13.189813 19.135303 c +14.054586 20.000078 l +10.660039 20.000078 l +10.292769 20.000078 9.995039 20.297810 9.995039 20.665077 c +9.995039 21.032347 10.292769 21.330078 10.660039 21.330078 c +14.054586 21.330078 l +13.189813 22.194853 l +12.930115 22.454552 12.930115 22.875607 13.189813 23.135303 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3084 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003174 00000 n +0000003197 00000 n +0000003370 00000 n +0000003444 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3503 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/Contents.json new file mode 100644 index 0000000000..602367a4a9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "customemoji_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/customemoji_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/customemoji_30.pdf new file mode 100644 index 0000000000..b06238e39f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/BoostPerk/EmojiPack.imageset/customemoji_30.pdf @@ -0,0 +1,207 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 15.000000 cm +0.000000 0.000000 0.000000 scn +2.764045 6.214447 m +2.730338 6.067720 2.629215 6.000000 2.505619 6.000000 c +2.370788 6.000000 2.269665 6.079007 2.247193 6.214447 c +2.084556 7.059471 2.008693 7.479908 1.761133 7.731189 c +1.512707 7.983349 1.091379 8.065167 0.235956 8.234763 c +0.089889 8.268623 0.000000 8.370203 0.000000 8.494356 c +0.000000 8.629797 0.089889 8.731377 0.235956 8.765237 c +1.884714 9.092119 1.889418 9.113951 2.211266 10.607844 c +2.222804 10.661399 2.234749 10.716846 2.247193 10.774266 c +2.269665 10.909706 2.370788 11.000000 2.505619 11.000000 c +2.640450 11.000000 2.730338 10.920993 2.764045 10.774266 c +2.773893 10.727359 2.783427 10.681768 2.792694 10.637450 c +3.111211 9.114224 3.115385 9.094264 4.764046 8.765237 c +4.910113 8.731377 5.000000 8.641084 5.000000 8.494356 c +5.000000 8.358916 4.910113 8.268623 4.764046 8.234763 c +3.069506 7.919125 3.067419 7.907477 2.765165 6.220693 c +2.764045 6.214447 l +h +21.255651 0.239583 m +21.234783 0.093750 21.151304 0.000000 20.994783 0.000000 c +20.848696 0.000000 20.775652 0.093750 20.733913 0.239583 c +20.725233 0.285484 20.716770 0.330369 20.708492 0.374265 c +20.348595 2.282850 20.341238 2.321868 18.240000 2.729167 c +18.093912 2.770833 18.000000 2.843750 18.000000 3.000000 c +18.000000 3.156250 18.093912 3.229167 18.240000 3.260417 c +20.297527 3.669214 20.309143 3.724892 20.681557 5.510261 c +20.698215 5.590123 20.715595 5.673446 20.733913 5.760417 c +20.775652 5.906250 20.848696 6.000000 20.994783 6.000000 c +21.151304 6.000000 21.234783 5.906250 21.255651 5.760417 c +21.268061 5.701499 21.280066 5.644257 21.291733 5.588629 c +21.682287 3.726425 21.693047 3.675112 23.760000 3.260417 c +23.916521 3.229167 24.000000 3.156250 24.000000 3.000000 c +24.000000 2.843750 23.916521 2.770833 23.760000 2.729167 c +21.639460 2.326447 21.631285 2.282002 21.265360 0.292361 c +21.255651 0.239583 l +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 2.834961 2.834961 cm +0.000000 0.000000 0.000000 scn +4.163339 19.470703 m +3.886738 19.167919 3.627093 18.849380 3.385908 18.516592 c +3.315481 18.882309 3.260239 19.161963 3.195358 19.380081 c +3.126438 19.611776 3.046642 19.774040 2.926216 19.896275 c +2.920539 19.902039 2.914770 19.907711 2.908909 19.913300 c +2.898950 19.922628 l +2.879345 19.940674 2.858713 19.957811 2.836943 19.974150 c +3.301094 20.527996 3.813655 21.039896 4.368117 21.503338 c +4.383039 21.484247 4.398642 21.466011 4.415012 21.448555 c +4.422750 21.440306 4.430657 21.432230 4.438746 21.424318 c +4.560323 21.305412 4.722594 21.223927 4.955741 21.151937 c +5.169733 21.085863 5.443433 21.027790 5.800178 20.956127 c +5.823530 20.951441 l +5.484449 20.706285 5.160123 20.441982 4.852137 20.160120 c +4.668910 20.098377 4.532844 20.025940 4.426920 19.925903 c +4.419153 19.918465 l +4.306369 19.808969 4.228240 19.666809 4.163339 19.470703 c +h +7.082016 21.736238 m +8.597874 22.542948 10.328053 23.000078 12.165000 23.000078 c +15.688613 23.000078 18.819374 21.318090 20.798193 18.713207 c +20.861319 18.821304 20.943417 18.934189 21.050665 19.041252 c +21.292435 19.282600 21.587986 19.421938 21.891327 19.472813 c +19.671530 22.422594 16.141024 24.330078 12.165000 24.330078 c +9.722339 24.330078 7.447829 23.610147 5.542076 22.370897 c +5.692157 22.335089 5.895671 22.293228 6.189427 22.234604 c +6.209517 22.230595 l +6.229474 22.225967 l +6.487258 22.166210 6.813300 22.030020 7.082016 21.736238 c +h +1.970473 18.805342 m +0.724305 16.896053 0.000000 14.615150 0.000000 12.165078 c +0.000000 5.446533 5.446456 0.000076 12.165000 0.000076 c +18.883545 0.000076 24.330002 5.446533 24.330002 12.165078 c +24.330002 12.582549 24.308968 12.995111 24.267912 13.401757 c +24.193945 13.383650 24.129078 13.366135 24.071005 13.348480 c +24.030434 13.336147 23.998871 13.325320 23.974834 13.316376 c +23.967258 13.294499 23.958279 13.266483 23.948175 13.231253 c +23.885868 13.013967 23.829411 12.710866 23.731909 12.181010 c +23.688400 11.915857 23.568218 11.566923 23.266214 11.274580 c +23.164131 11.175761 23.054655 11.096324 22.941631 11.033643 c +22.375893 5.581362 17.766920 1.330078 12.165000 1.330078 c +6.180995 1.330078 1.330000 6.181072 1.330000 12.165078 c +1.330000 14.001682 1.786960 15.731558 2.593387 17.247211 c +2.320833 17.495625 2.162627 17.816124 2.104306 18.138243 c +2.045608 18.443207 2.005440 18.651093 1.970473 18.805342 c +h +22.839943 14.030951 m +22.854223 14.056534 22.869083 14.081108 22.884594 14.104742 c +22.980665 14.251131 23.101734 14.361477 23.265259 14.452059 c +23.438992 14.548294 23.660646 14.622222 23.951162 14.693368 c +23.986843 14.702106 24.023561 14.710802 24.061359 14.719493 c +23.992277 15.042780 23.910357 15.361301 23.816145 15.674510 c +23.594534 15.737709 23.419144 15.804732 23.276915 15.887229 c +23.103924 15.987570 22.979992 16.110804 22.881632 16.277901 c +22.781334 16.448290 22.707623 16.664288 22.635593 16.948122 c +22.578993 17.171165 22.523428 17.436102 22.456816 17.753716 c +22.454109 17.766624 l +22.444901 17.810524 l +22.420734 17.925503 l +22.399866 18.071337 22.316387 18.165087 22.159866 18.165087 c +22.013779 18.165087 21.940735 18.071337 21.898996 17.925503 c +21.882824 17.848526 l +21.874866 17.810532 l +21.846638 17.675344 l +21.812077 17.509653 21.780622 17.358860 21.750351 17.221300 c +22.270237 16.237764 22.643177 15.164577 22.839943 14.030951 c +h +12.164252 9.958713 m +14.317219 9.958713 16.264034 10.424266 17.662300 10.758640 c +19.028076 11.085245 19.870502 11.286700 19.870502 10.806913 c +19.870502 6.550869 16.420296 3.100664 12.164252 3.100664 c +7.908207 3.100664 4.458001 6.550869 4.458001 10.806913 c +4.458001 11.286698 5.300421 11.085247 6.666188 10.758643 c +6.666201 10.758640 l +6.666217 10.758637 l +8.064482 10.424262 10.011292 9.958713 12.164252 9.958713 c +h +16.926613 9.254669 m +15.850132 8.972999 14.200197 8.541277 12.155004 8.541277 c +10.109828 8.541277 8.459906 8.972991 7.383423 9.254661 c +7.383405 9.254666 l +7.383395 9.254669 l +6.488583 9.488805 5.990004 9.619263 5.990004 9.311902 c +5.990004 6.361948 10.471958 6.229403 12.155004 6.229403 c +14.044576 6.229403 18.320004 6.362463 18.320004 9.311902 c +18.320004 9.619263 17.821424 9.488805 16.926613 9.254669 c +h +8.665002 13.415092 m +9.493429 13.415092 10.165002 14.198593 10.165002 15.165092 c +10.165002 16.131590 9.493429 16.915092 8.665002 16.915092 c +7.836575 16.915092 7.165002 16.131590 7.165002 15.165092 c +7.165002 14.198593 7.836575 13.415092 8.665002 13.415092 c +h +17.165001 15.165092 m +17.165001 14.198593 16.493429 13.415092 15.665002 13.415092 c +14.836575 13.415092 14.165002 14.198593 14.165002 15.165092 c +14.165002 16.131590 14.836575 16.915092 15.665002 16.915092 c +16.493429 16.915092 17.165001 16.131590 17.165001 15.165092 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 6708 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000006798 00000 n +0000006821 00000 n +0000006994 00000 n +0000007068 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +7127 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/Contents.json new file mode 100644 index 0000000000..7f8630ed82 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "boost_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/boost_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/boost_30.pdf new file mode 100644 index 0000000000..fd9103e865 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Boost.imageset/boost_30.pdf @@ -0,0 +1,116 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 15.834961 4.834961 cm +0.000000 0.000000 0.000000 scn +4.830000 7.665078 m +4.830000 8.032348 4.532269 8.330078 4.165000 8.330078 c +3.797731 8.330078 3.500000 8.032348 3.500000 7.665078 c +3.500000 4.830078 l +0.665000 4.830078 l +0.297731 4.830078 0.000000 4.532348 0.000000 4.165078 c +0.000000 3.797809 0.297731 3.500078 0.665000 3.500078 c +3.500000 3.500078 l +3.500000 0.665078 l +3.500000 0.297809 3.797731 0.000078 4.165000 0.000078 c +4.532269 0.000078 4.830000 0.297809 4.830000 0.665078 c +4.830000 3.500078 l +7.665000 3.500078 l +8.032269 3.500078 8.330000 3.797809 8.330000 4.165078 c +8.330000 4.532348 8.032269 4.830078 7.665000 4.830078 c +4.830000 4.830078 l +4.830000 7.665078 l +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 8.511719 3.673828 cm +0.000000 0.000000 0.000000 scn +8.062260 14.252642 m +7.985591 13.767072 8.360885 13.327871 8.852470 13.327871 c +12.167960 13.327871 l +12.811290 13.327871 13.191483 12.607067 12.828204 12.076122 c +11.935228 10.771004 l +11.791506 10.803908 11.641865 10.821289 11.488164 10.821289 c +10.386356 10.821289 9.493164 9.928097 9.493164 8.826289 c +9.493164 7.321289 l +7.988164 7.321289 l +6.886356 7.321289 5.993164 6.428097 5.993164 5.326290 c +5.993164 4.576958 6.406287 3.924118 7.017194 3.583107 c +5.288596 1.056694 l +4.806934 0.352728 3.705108 0.790676 3.838140 1.633213 c +4.907070 8.403103 l +4.983739 8.888673 4.608444 9.327872 4.116858 9.327872 c +0.801369 9.327872 l +0.158039 9.327872 -0.222153 10.048677 0.141125 10.579621 c +7.680734 21.599051 l +8.162395 22.303017 9.264221 21.865067 9.131189 21.022530 c +8.062260 14.252642 l +h +8.664898 5.991289 m +7.778071 4.695158 l +7.513759 4.783100 7.323164 5.032436 7.323164 5.326290 c +7.323164 5.693558 7.620895 5.991289 7.988164 5.991289 c +8.664898 5.991289 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1821 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001911 00000 n +0000001934 00000 n +0000002107 00000 n +0000002181 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2240 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/Contents.json new file mode 100644 index 0000000000..0ed80ac1b2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "about_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/about_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/about_30.pdf new file mode 100644 index 0000000000..ee0c4d4620 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Features.imageset/about_30.pdf @@ -0,0 +1,85 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +-1.000000 0.000000 -0.000000 -1.000000 24.666992 25.000000 cm +0.000000 0.000000 0.000000 scn +10.000000 0.000000 m +15.522847 0.000000 20.000000 4.477153 20.000000 10.000000 c +20.000000 15.522848 15.522847 20.000000 10.000000 20.000000 c +4.477152 20.000000 0.000000 15.522848 0.000000 10.000000 c +0.000000 4.477153 4.477152 0.000000 10.000000 0.000000 c +h +10.000000 16.499992 m +10.552284 16.499992 11.000000 16.052279 11.000000 15.499993 c +11.000000 8.499993 l +11.000000 7.947709 10.552284 7.499993 10.000000 7.499993 c +9.447715 7.499993 9.000000 7.947709 9.000000 8.499993 c +9.000000 15.499993 l +9.000000 16.052279 9.447715 16.499992 10.000000 16.499992 c +h +10.000000 3.749992 m +10.690356 3.749992 11.250000 4.309637 11.250000 4.999993 c +11.250000 5.690350 10.690356 6.249993 10.000000 6.249993 c +9.309644 6.249993 8.750000 5.690350 8.750000 4.999993 c +8.750000 4.309637 9.309644 3.749992 10.000000 3.749992 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 948 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001038 00000 n +0000001060 00000 n +0000001233 00000 n +0000001307 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1366 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/Contents.json new file mode 100644 index 0000000000..cff8af9df5 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "giveaway_30 (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/giveaway_30 (2).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/giveaway_30 (2).pdf new file mode 100644 index 0000000000..d32aef4b3f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Boosts/Giveaway.imageset/giveaway_30 (2).pdf @@ -0,0 +1,186 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 7.428711 20.093262 cm +0.000000 0.000000 0.000000 scn +6.271209 0.000062 m +4.615478 0.000062 l +3.771815 0.000062 3.116881 0.213802 2.650675 0.641283 c +2.184469 1.068763 1.951365 1.570652 1.951365 2.146948 c +1.951365 2.710829 2.139227 3.152450 2.514951 3.471810 c +2.890676 3.791170 3.377159 3.950850 3.974400 3.950850 c +4.607714 3.950850 5.148844 3.728631 5.597790 3.284194 c +6.046736 2.839757 6.271209 2.243457 6.271209 1.495295 c +6.271209 0.000062 l +8.333720 0.000062 l +8.333720 1.495295 l +8.333720 2.243457 8.416081 2.839757 8.865445 3.284194 c +9.314810 3.728631 9.859194 3.950850 10.498600 3.950850 c +11.089033 3.950850 11.571902 3.791170 11.947208 3.471810 c +12.322515 3.152450 12.510168 2.710829 12.510168 2.146948 c +12.510168 1.570652 12.277244 1.068763 11.811396 0.641283 c +11.345549 0.213802 10.690793 0.000062 9.847131 0.000062 c +8.333720 0.000062 l +13.456915 0.000062 l +13.768794 0.289877 14.014379 0.629154 14.193670 1.017893 c +14.372962 1.406632 14.462607 1.832761 14.462607 2.296282 c +14.462607 2.976503 14.291677 3.581712 13.949817 4.111909 c +13.607956 4.642107 13.149544 5.056827 12.574580 5.356069 c +11.999616 5.655311 11.356059 5.804932 10.643909 5.804932 c +9.859492 5.804932 9.163199 5.604929 8.555029 5.204925 c +7.946858 4.804920 7.507379 4.239113 7.236590 3.507505 c +6.965801 4.239113 6.524559 4.804920 5.912865 5.204925 c +5.301171 5.604929 4.603175 5.804932 3.818878 5.804932 c +3.113536 5.804932 2.471682 5.655311 1.893314 5.356069 c +1.314945 5.056827 0.854801 4.642107 0.512880 4.111909 c +0.170960 3.581712 0.000000 2.976503 0.000000 2.296282 c +0.000000 1.832761 0.089646 1.406632 0.268938 1.017893 c +0.448229 0.629154 0.693875 0.289877 1.005873 0.000062 c +6.271209 0.000062 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 4.399414 3.928711 cm +0.000000 0.000000 0.000000 scn +9.297392 16.184326 m +9.297392 13.233196 l +9.297392 13.205330 9.304155 13.177880 9.317101 13.153204 c +9.361280 13.069000 9.465354 13.036554 9.549558 13.080732 c +10.010468 13.322554 l +10.210828 13.427675 10.450042 13.427675 10.650402 13.322554 c +11.111313 13.080732 l +11.135988 13.067786 11.163439 13.061022 11.191304 13.061022 c +11.286394 13.061022 11.363479 13.138107 11.363479 13.233196 c +11.363479 16.184326 l +19.283478 16.184326 l +20.044191 16.184326 20.660870 15.567647 20.660870 14.806935 c +20.660870 12.052153 l +20.660870 11.291441 20.044191 10.674761 19.283478 10.674761 c +18.594784 10.674761 l +18.594933 10.657510 l +18.539255 10.668822 18.481627 10.674761 18.422609 10.674761 c +15.055919 10.675272 l +15.696681 10.485103 16.147873 9.883333 16.129395 9.195256 c +16.125700 9.122501 l +16.121429 9.065297 16.113916 9.008687 16.103285 8.952910 c +18.422609 8.953022 l +18.481627 8.953022 18.539255 8.958961 18.594933 8.970274 c +18.594784 2.754762 l +18.594784 1.233337 17.361425 -0.000021 15.840000 -0.000021 c +11.363479 -0.000021 l +11.363479 2.804149 l +11.363479 3.021412 11.261068 3.224681 11.089363 3.354087 c +11.040842 3.387503 l +10.513464 3.718435 l +10.401549 3.788662 10.259320 3.788662 10.147406 3.718435 c +9.620029 3.387503 l +9.419268 3.261524 9.297392 3.041163 9.297392 2.804149 c +9.297392 -0.000021 l +4.820869 -0.000021 l +3.299445 -0.000021 2.066087 1.233337 2.066087 2.754762 c +2.066087 8.953022 l +4.572276 8.953012 l +4.497755 9.343477 4.577706 9.761106 4.823255 10.106071 c +4.874576 10.174066 l +5.066670 10.414354 5.326026 10.587791 5.615777 10.674929 c +1.893913 10.674761 l +1.377391 10.674761 l +0.616679 10.674761 0.000000 11.291441 0.000000 12.052153 c +0.000000 14.806935 l +-0.000000 15.567647 0.616679 16.184326 1.377391 16.184326 c +9.297392 16.184326 l +h +9.899277 12.092336 m +8.916199 9.828533 l +8.846663 9.668410 8.694118 9.560011 8.520034 9.547014 c +6.013629 9.359897 l +5.883370 9.350172 5.763032 9.286686 5.681469 9.184660 c +5.519364 8.981886 5.552333 8.686092 5.755107 8.523988 c +6.579829 7.864677 l +6.972448 7.550803 7.477788 7.414086 7.975103 7.487194 c +10.210711 7.815840 l +10.304756 7.829664 10.396884 7.780811 10.438204 7.695207 c +10.490416 7.587036 10.445051 7.457020 10.336881 7.404807 c +8.412390 6.475896 l +7.984262 6.269247 7.667592 5.886102 7.545246 5.426723 c +7.218948 4.201552 l +7.186707 4.080493 7.204124 3.951572 7.267334 3.843410 c +7.398319 3.619270 7.686204 3.543755 7.910343 3.674740 c +10.093266 4.950423 l +10.239786 5.036049 10.421084 5.036049 10.567604 4.950423 c +12.768568 3.664197 l +12.874888 3.602064 13.001345 3.584135 13.120747 3.614265 c +13.372462 3.677783 13.525026 3.933331 13.461508 4.185046 c +12.875005 6.509303 l +12.830278 6.686551 12.892370 6.873654 13.034194 6.988995 c +14.923353 8.525375 l +15.023630 8.606926 15.085902 8.726170 15.095525 8.855062 c +15.114852 9.113949 14.920650 9.339484 14.661765 9.358811 c +12.140836 9.547014 l +11.966751 9.560011 11.814207 9.668410 11.744673 9.828533 c +10.761594 12.092336 l +10.714136 12.201618 10.626952 12.288803 10.517670 12.336260 c +10.279548 12.439667 10.002684 12.330458 9.899277 12.092336 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 4957 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000005047 00000 n +0000005070 00000 n +0000005243 00000 n +0000005317 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +5376 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/Contents.json new file mode 100644 index 0000000000..f1ac78866d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "gift_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/gift_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/gift_30.pdf new file mode 100644 index 0000000000..53c86d4359 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/gift_30.pdf @@ -0,0 +1,443 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 6.250000 3.669434 cm +0.000000 0.000000 0.000000 scn +0.665000 11.330566 m +0.665000 11.697836 0.367269 11.995566 0.000000 11.995566 c +-0.367269 11.995566 -0.665000 11.697836 -0.665000 11.330566 c +0.665000 11.330566 l +h +18.165001 11.330566 m +18.165001 11.697836 17.867270 11.995566 17.500000 11.995566 c +17.132730 11.995566 16.834999 11.697836 16.834999 11.330566 c +18.165001 11.330566 l +h +1.365024 1.603050 m +1.666927 2.195569 l +1.365024 1.603050 l +h +0.272484 2.695590 m +0.865003 2.997494 l +0.272484 2.695590 l +h +16.134975 1.603050 m +16.436880 1.010530 l +16.134975 1.603050 l +h +17.227516 2.695590 m +17.820036 2.393686 l +17.227516 2.695590 l +h +13.500000 1.995566 m +4.000000 1.995566 l +4.000000 0.665566 l +13.500000 0.665566 l +13.500000 1.995566 l +h +0.665000 5.330566 m +0.665000 11.330566 l +-0.665000 11.330566 l +-0.665000 5.330566 l +0.665000 5.330566 l +h +16.834999 11.330566 m +16.834999 5.330566 l +18.165001 5.330566 l +18.165001 11.330566 l +16.834999 11.330566 l +h +4.000000 1.995566 m +3.288961 1.995566 2.795676 1.996083 2.412157 2.027418 c +2.036401 2.058119 1.824946 2.115055 1.666927 2.195569 c +1.063120 1.010530 l +1.439881 0.818562 1.845848 0.739256 2.303853 0.701836 c +2.754094 0.665050 3.310907 0.665566 4.000000 0.665566 c +4.000000 1.995566 l +h +-0.665000 5.330566 m +-0.665000 4.641474 -0.665517 4.084661 -0.628731 3.634419 c +-0.591311 3.176414 -0.512005 2.770447 -0.320036 2.393686 c +0.865003 2.997494 l +0.784489 3.155513 0.727552 3.366968 0.696852 3.742723 c +0.665517 4.126243 0.665000 4.619528 0.665000 5.330566 c +-0.665000 5.330566 l +h +1.666927 2.195569 m +1.321650 2.371497 1.040931 2.652216 0.865003 2.997494 c +-0.320036 2.393686 l +-0.016596 1.798154 0.467587 1.313970 1.063120 1.010530 c +1.666927 2.195569 l +h +13.500000 0.665566 m +14.189093 0.665566 14.745906 0.665050 15.196147 0.701836 c +15.654152 0.739256 16.060120 0.818562 16.436880 1.010530 c +15.833073 2.195569 l +15.675054 2.115055 15.463599 2.058119 15.087843 2.027418 c +14.704324 1.996083 14.211039 1.995566 13.500000 1.995566 c +13.500000 0.665566 l +h +16.834999 5.330566 m +16.834999 4.619527 16.834482 4.126243 16.803148 3.742723 c +16.772448 3.366968 16.715511 3.155513 16.634996 2.997494 c +17.820036 2.393686 l +18.012005 2.770447 18.091311 3.176414 18.128731 3.634419 c +18.165518 4.084660 18.165001 4.641474 18.165001 5.330566 c +16.834999 5.330566 l +h +16.436880 1.010530 m +17.032413 1.313970 17.516596 1.798154 17.820036 2.393686 c +16.634996 2.997494 l +16.459070 2.652216 16.178350 2.371497 15.833073 2.195569 c +16.436880 1.010530 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 5.000000 14.293945 cm +0.000000 0.000000 0.000000 scn +2.500000 0.666055 m +2.867269 0.666055 3.165000 0.963785 3.165000 1.331055 c +3.165000 1.698324 2.867269 1.996055 2.500000 1.996055 c +2.500000 0.666055 l +h +17.500000 1.996055 m +17.132730 1.996055 16.834999 1.698324 16.834999 1.331055 c +16.834999 0.963785 17.132730 0.666055 17.500000 0.666055 c +17.500000 1.996055 l +h +0.144016 2.189722 m +-0.457137 1.905398 l +0.144016 2.189722 l +h +0.858667 1.475071 m +1.142991 2.076224 l +0.858667 1.475071 l +h +19.855984 2.189722 m +20.457136 1.905398 l +19.855984 2.189722 l +h +19.141333 1.475071 m +18.857008 2.076224 l +19.141333 1.475071 l +h +18.568888 7.341028 m +18.284563 6.739875 l +18.568888 7.341028 l +h +19.759974 6.149942 m +19.158821 5.865618 l +19.759974 6.149942 l +h +3.906250 6.916055 m +16.093750 6.916055 l +16.093750 8.246055 l +3.906250 8.246055 l +3.906250 6.916055 l +h +2.343750 0.666055 m +2.500000 0.666055 l +2.500000 1.996055 l +2.343750 1.996055 l +2.343750 0.666055 l +h +17.656250 1.996055 m +17.500000 1.996055 l +17.500000 0.666055 l +17.656250 0.666055 l +17.656250 1.996055 l +h +-0.665000 3.674805 m +-0.665000 3.291543 -0.665455 2.966613 -0.645052 2.700472 c +-0.624109 2.427277 -0.578354 2.161689 -0.457137 1.905398 c +0.745169 2.474046 l +0.722370 2.522251 0.696117 2.605676 0.681057 2.802133 c +0.665455 3.005646 0.665000 3.271009 0.665000 3.674805 c +-0.665000 3.674805 l +h +2.343750 1.996055 m +1.939954 1.996055 1.674591 1.996510 1.471079 2.012111 c +1.274621 2.027172 1.191196 2.053424 1.142991 2.076224 c +0.574343 0.873918 l +0.830635 0.752701 1.096222 0.706945 1.369418 0.686002 c +1.635558 0.665600 1.960488 0.666055 2.343750 0.666055 c +2.343750 1.996055 l +h +-0.457137 1.905398 m +-0.242981 1.452604 0.121549 1.088073 0.574343 0.873918 c +1.142991 2.076224 l +0.968357 2.158820 0.827765 2.299412 0.745169 2.474046 c +-0.457137 1.905398 l +h +19.334999 3.674805 m +19.334999 3.271009 19.334545 3.005646 19.318943 2.802133 c +19.303883 2.605676 19.277630 2.522251 19.254831 2.474046 c +20.457136 1.905398 l +20.578354 2.161689 20.624109 2.427277 20.645052 2.700472 c +20.665455 2.966613 20.665001 3.291543 20.665001 3.674805 c +19.334999 3.674805 l +h +17.656250 0.666055 m +18.039513 0.666055 18.364443 0.665600 18.630583 0.686002 c +18.903778 0.706945 19.169365 0.752701 19.425657 0.873918 c +18.857008 2.076224 l +18.808804 2.053424 18.725378 2.027172 18.528921 2.012111 c +18.325409 1.996510 18.060045 1.996055 17.656250 1.996055 c +17.656250 0.666055 l +h +19.254831 2.474046 m +19.172235 2.299412 19.031643 2.158820 18.857008 2.076224 c +19.425657 0.873918 l +19.878450 1.088073 20.242981 1.452604 20.457136 1.905398 c +19.254831 2.474046 l +h +16.093750 6.916055 m +16.759899 6.916055 17.222025 6.915600 17.582088 6.887997 c +17.935097 6.860935 18.134859 6.810679 18.284563 6.739875 c +18.853212 7.942181 l +18.495422 8.111403 18.113497 8.181161 17.683750 8.214106 c +17.261059 8.246510 16.739365 8.246055 16.093750 8.246055 c +16.093750 6.916055 l +h +20.665001 3.674805 m +20.665001 4.320419 20.665455 4.842113 20.633051 5.264805 c +20.600107 5.694551 20.530348 6.076477 20.361126 6.434267 c +19.158821 5.865618 l +19.229626 5.715915 19.279881 5.516152 19.306942 5.163144 c +19.334545 4.803081 19.334999 4.340953 19.334999 3.674805 c +20.665001 3.674805 l +h +18.284563 6.739875 m +18.668341 6.558362 18.977308 6.249395 19.158821 5.865618 c +20.361126 6.434267 l +20.048054 7.096203 19.515148 7.629108 18.853212 7.942181 c +18.284563 6.739875 l +h +3.906250 8.246055 m +3.260636 8.246055 2.738941 8.246510 2.316250 8.214106 c +1.886503 8.181161 1.504578 8.111403 1.146788 7.942181 c +1.715436 6.739875 l +1.865140 6.810679 2.064903 6.860935 2.417911 6.887997 c +2.777974 6.915600 3.240102 6.916055 3.906250 6.916055 c +3.906250 8.246055 l +h +0.665000 3.674805 m +0.665000 4.340953 0.665455 4.803081 0.693058 5.163144 c +0.720120 5.516152 0.770375 5.715915 0.841180 5.865618 c +-0.361126 6.434267 l +-0.530348 6.076477 -0.600106 5.694551 -0.633051 5.264805 c +-0.665455 4.842113 -0.665000 4.320419 -0.665000 3.674805 c +0.665000 3.674805 l +h +1.146788 7.942181 m +0.484851 7.629108 -0.048053 7.096203 -0.361126 6.434267 c +0.841180 5.865618 l +1.022693 6.249395 1.331660 6.558362 1.715436 6.739875 c +1.146788 7.942181 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 20.544922 cm +0.000000 0.000000 0.000000 scn +5.000000 1.330078 m +5.000000 0.665078 l +5.665000 0.665078 l +5.665000 1.330078 l +5.000000 1.330078 l +h +4.335000 3.830078 m +4.335000 1.330078 l +5.665000 1.330078 l +5.665000 3.830078 l +4.335000 3.830078 l +h +5.000000 1.995078 m +2.500000 1.995078 l +2.500000 0.665078 l +5.000000 0.665078 l +5.000000 1.995078 l +h +2.500000 1.995078 m +1.486557 1.995078 0.665000 2.816636 0.665000 3.830078 c +-0.665000 3.830078 l +-0.665000 2.082097 0.752019 0.665078 2.500000 0.665078 c +2.500000 1.995078 l +h +2.500000 5.665078 m +3.513443 5.665078 4.335000 4.843521 4.335000 3.830078 c +5.665000 3.830078 l +5.665000 5.578059 4.247981 6.995078 2.500000 6.995078 c +2.500000 5.665078 l +h +2.500000 6.995078 m +0.752019 6.995078 -0.665000 5.578059 -0.665000 3.830078 c +0.665000 3.830078 l +0.665000 4.843521 1.486557 5.665078 2.500000 5.665078 c +2.500000 6.995078 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 15.000000 20.544922 cm +0.000000 0.000000 0.000000 scn +0.000000 1.330078 m +-0.665000 1.330078 l +-0.665000 0.665078 l +0.000000 0.665078 l +0.000000 1.330078 l +h +2.500000 1.995078 m +0.000000 1.995078 l +0.000000 0.665078 l +2.500000 0.665078 l +2.500000 1.995078 l +h +0.665000 1.330078 m +0.665000 3.830078 l +-0.665000 3.830078 l +-0.665000 1.330078 l +0.665000 1.330078 l +h +4.335000 3.830078 m +4.335000 2.816636 3.513443 1.995078 2.500000 1.995078 c +2.500000 0.665078 l +4.247981 0.665078 5.665000 2.082097 5.665000 3.830078 c +4.335000 3.830078 l +h +2.500000 5.665078 m +3.513443 5.665078 4.335000 4.843521 4.335000 3.830078 c +5.665000 3.830078 l +5.665000 5.578059 4.247981 6.995078 2.500000 6.995078 c +2.500000 5.665078 l +h +2.500000 6.995078 m +0.752019 6.995078 -0.665000 5.578059 -0.665000 3.830078 c +0.665000 3.830078 l +0.665000 4.843521 1.486557 5.665078 2.500000 5.665078 c +2.500000 6.995078 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 15.000000 4.294922 cm +0.000000 0.000000 0.000000 scn +0.665000 3.830078 m +0.665000 4.197348 0.367269 4.495078 0.000000 4.495078 c +-0.367269 4.495078 -0.665000 4.197348 -0.665000 3.830078 c +0.665000 3.830078 l +h +-0.665000 1.330078 m +-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c +0.367269 0.665078 0.665000 0.962809 0.665000 1.330078 c +-0.665000 1.330078 l +h +-0.665000 3.830078 m +-0.665000 1.330078 l +0.665000 1.330078 l +0.665000 3.830078 l +-0.665000 3.830078 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 9.990234 9.851074 cm +0.000000 0.000000 0.000000 scn +4.751247 1.595008 m +2.492245 0.211133 l +2.257351 0.067236 1.950281 0.141003 1.806384 0.375896 c +1.736104 0.490619 1.715151 0.628868 1.748279 0.759263 c +2.097972 2.135665 l +2.224205 2.632523 2.564205 3.047855 3.026343 3.269736 c +5.490801 4.452966 l +5.605695 4.508129 5.654118 4.645987 5.598955 4.760881 c +5.554282 4.853927 5.453256 4.905994 5.351554 4.888387 c +2.608295 4.413459 l +2.050655 4.316917 1.478800 4.470905 1.045039 4.834409 c +0.178421 5.560659 l +-0.032710 5.737593 -0.060432 6.052180 0.116501 6.263311 c +0.202556 6.365997 0.326303 6.429747 0.459872 6.440200 c +3.107648 6.647414 l +3.294706 6.662054 3.457723 6.780433 3.529533 6.953778 c +4.550995 9.419525 l +4.656422 9.674018 4.948194 9.794860 5.202687 9.689434 c +5.324886 9.638812 5.421974 9.541724 5.472596 9.419525 c +6.494058 6.953778 l +6.565868 6.780433 6.728885 6.662054 6.915944 6.647414 c +9.578268 6.439061 l +9.852894 6.417569 10.058100 6.177518 10.036608 5.902892 c +10.026269 5.770781 9.963782 5.648214 9.862940 5.562243 c +7.832497 3.831244 l +7.689560 3.709387 7.627202 3.517563 7.671160 3.334950 c +8.295380 0.741805 l +8.359848 0.473989 8.195003 0.204619 7.927186 0.140151 c +7.798499 0.109174 7.662776 0.130618 7.549908 0.199762 c +5.272344 1.595008 l +5.112454 1.692957 4.911138 1.692957 4.751247 1.595008 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 10525 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000010615 00000 n +0000010639 00000 n +0000010812 00000 n +0000010886 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +10945 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/LastSeen.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/LastSeen.imageset/Contents.json new file mode 100644 index 0000000000..443d8f2b1f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/LastSeen.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "lastseen_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/LastSeen.imageset/lastseen_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/LastSeen.imageset/lastseen_30.pdf new file mode 100644 index 0000000000..2087b89da7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/LastSeen.imageset/lastseen_30.pdf @@ -0,0 +1,169 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 30.000000 30.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.320312 4.036133 cm +0.000000 0.000000 0.000000 scn +15.718013 7.249816 m +15.405374 7.990487 12.549557 9.993635 11.723054 10.108183 c +11.210857 10.179170 10.094700 8.757028 9.820968 7.139155 c +9.652993 6.146357 9.900538 5.153749 10.575783 4.550840 c +12.351411 2.965429 16.030653 6.509145 15.718013 7.249816 c +h +20.036879 19.735321 m +18.253841 20.338758 14.037626 18.223207 12.869061 12.033148 c +12.869061 11.732358 15.300022 9.989953 16.974861 9.388252 c +17.829952 9.081054 20.971378 12.265821 21.642756 15.454392 c +22.042732 17.354000 20.948334 19.426855 20.036879 19.735321 c +h +5.331334 5.154034 m +4.841030 5.573311 1.180003 6.640569 0.478038 6.303137 c +-0.223926 5.965705 -0.489510 1.657463 2.111791 1.081186 c +4.713092 0.504911 5.821639 4.734756 5.331334 5.154034 c +h +1.053483 8.698494 m +1.543788 8.279216 5.287134 6.983948 5.989099 7.321380 c +6.691063 7.658812 11.170075 16.921753 6.587785 17.884901 c +5.138152 18.189598 2.305758 16.301899 1.450079 13.825531 c +0.615815 11.411140 0.811438 8.905476 1.053483 8.698494 c +h +f* +n +Q + +endstream +endobj + +2 0 obj + 1098 +endobj + +3 0 obj + << /Type /XObject + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 30.000000 30.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 18.799999 m +0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c +1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c +5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c +18.799999 30.000000 l +22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c +27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c +30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c +30.000000 11.200001 l +30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c +28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c +24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c +11.200000 0.000000 l +7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c +2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c +0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c +0.000000 18.799999 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 944 +endobj + +5 0 obj + << /XObject << /X1 1 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 3 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +6 0 obj + << /Length 7 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +7 0 obj + 46 +endobj + +8 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 5 0 R + /Contents 6 0 R + /Parent 9 0 R + >> +endobj + +9 0 obj + << /Kids [ 8 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +10 0 obj + << /Pages 9 0 R + /Type /Catalog + >> +endobj + +xref +0 11 +0000000000 65535 f +0000000010 00000 n +0000001356 00000 n +0000001379 00000 n +0000002571 00000 n +0000002593 00000 n +0000002891 00000 n +0000002993 00000 n +0000003014 00000 n +0000003187 00000 n +0000003261 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 10 0 R + /Size 11 +>> +startxref +3321 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessagePrivacy.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessagePrivacy.imageset/Contents.json new file mode 100644 index 0000000000..fe71658b83 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessagePrivacy.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "messageprivacy_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessagePrivacy.imageset/messageprivacy_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessagePrivacy.imageset/messageprivacy_30.pdf new file mode 100644 index 0000000000..7a037b9846 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessagePrivacy.imageset/messageprivacy_30.pdf @@ -0,0 +1,185 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 30.000000 30.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.000000 4.484375 cm +0.000000 0.000000 0.000000 scn +10.000000 20.135742 m +15.522848 20.135742 20.000000 16.065603 20.000000 11.044833 c +20.000000 6.024063 15.522848 1.953924 10.000000 1.953924 c +9.153261 1.953924 8.331102 2.049595 7.545889 2.229696 c +7.397357 2.263765 7.228708 2.107983 6.947171 1.847927 c +6.606689 1.533424 6.101101 1.066412 5.266314 0.598253 c +4.199766 0.000120 2.722059 0.052011 2.471971 0.156912 c +2.232201 0.257484 2.416753 0.457399 2.741760 0.809465 c +2.966608 1.053034 3.258681 1.369423 3.523984 1.776117 c +4.172771 2.770672 3.904685 3.954702 3.613619 4.167799 c +1.337573 5.834144 0.000000 8.181331 0.000000 11.044833 c +0.000000 16.065603 4.477152 20.135742 10.000000 20.135742 c +h +6.335000 14.010747 m +6.335000 16.034870 7.975876 17.675747 10.000000 17.675747 c +12.024124 17.675747 13.665000 16.034870 13.665000 14.010747 c +13.665000 12.722709 l +14.121655 12.482201 14.492952 12.106080 14.727516 11.645723 c +15.000000 11.110944 15.000000 10.410878 15.000000 9.010747 c +15.000000 7.610616 15.000000 6.910550 14.727516 6.375771 c +14.487833 5.905365 14.105381 5.522914 13.634976 5.283231 c +13.100197 5.010747 12.400131 5.010747 11.000000 5.010747 c +9.000000 5.010747 l +7.599869 5.010747 6.899803 5.010747 6.365024 5.283231 c +5.894619 5.522914 5.512167 5.905365 5.272484 6.375771 c +5.000000 6.910550 5.000000 7.610616 5.000000 9.010747 c +5.000000 10.410878 5.000000 11.110944 5.272484 11.645723 c +5.507047 12.106080 5.878345 12.482201 6.335000 12.722709 c +6.335000 14.010747 l +h +12.335000 14.010747 m +12.335000 12.995256 l +11.973238 13.010747 11.538372 13.010747 11.000000 13.010747 c +9.000000 13.010747 l +8.461628 13.010747 8.026762 13.010747 7.665000 12.995256 c +7.665000 14.010747 l +7.665000 15.300332 8.710415 16.345747 10.000000 16.345747 c +11.289585 16.345747 12.335000 15.300332 12.335000 14.010747 c +h +f* +n +Q + +endstream +endobj + +2 0 obj + 1905 +endobj + +3 0 obj + << /Type /XObject + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 30.000000 30.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 18.799999 m +0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c +1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c +5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c +18.799999 30.000000 l +22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c +27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c +30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c +30.000000 11.200001 l +30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c +28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c +24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c +11.200000 0.000000 l +7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c +2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c +0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c +0.000000 18.799999 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 944 +endobj + +5 0 obj + << /XObject << /X1 1 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 3 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +6 0 obj + << /Length 7 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +7 0 obj + 46 +endobj + +8 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 5 0 R + /Contents 6 0 R + /Parent 9 0 R + >> +endobj + +9 0 obj + << /Kids [ 8 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +10 0 obj + << /Pages 9 0 R + /Type /Catalog + >> +endobj + +xref +0 11 +0000000000 65535 f +0000000010 00000 n +0000002163 00000 n +0000002186 00000 n +0000003378 00000 n +0000003400 00000 n +0000003698 00000 n +0000003800 00000 n +0000003821 00000 n +0000003994 00000 n +0000004068 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 10 0 R + /Size 11 +>> +startxref +4128 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f2cbac933d..b5a9a95f58 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -38,7 +38,6 @@ import BotPaymentsUI import DeleteChatPeerActionSheetItem import HashtagSearchUI import LegacyMediaPickerUI -import WebSearchUI import Emoji import PeerAvatarGalleryUI import PeerInfoUI @@ -76,7 +75,6 @@ import ChatSendMessageActionUI import ChatTextLinkEditUI import WebUI import PremiumUI -import PremiumGiftAttachmentScreen import ImageTransparency import StickerPackPreviewUI import TextNodeWithEntities @@ -93,8 +91,6 @@ import StorageUsageScreen import AvatarEditorScreen import ChatScheduleTimeController import ICloudResources -import LegacyCamera -import LegacyInstantVideoController import StoryContainerScreen import MoreHeaderButton import VolumeButtons @@ -123,6 +119,7 @@ import WallpaperGridScreen import VideoMessageCameraScreen import TopMessageReactions import PeerInfoScreen +import AudioWaveform public enum ChatControllerPeekActions { case standard @@ -442,7 +439,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G weak var messageTooltipController: TooltipController? weak var videoUnmuteTooltipController: TooltipController? var didDisplayVideoUnmuteTooltip = false + var didDisplayGroupEmojiTip = false var didDisplaySendWhenOnlineTip = false + let displaySendWhenOnlineTipDisposable = MetaDisposable() + weak var silentPostTooltipController: TooltipController? weak var mediaRecordingModeTooltipController: TooltipController? weak var mediaRestrictedTooltipController: TooltipController? @@ -1074,6 +1074,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G openMessageByAction = true } } + case .boostsApplied: + strongSelf.controllerInteraction?.openGroupBoostInfo(nil, 0) + return true default: break } @@ -1637,7 +1640,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, sendCurrentMessage: { [weak self] silentPosting in if let strongSelf = self { - if let _ = strongSelf.presentationInterfaceState.recordedMediaPreview { + if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState { strongSelf.sendMediaRecording(silentPosting: silentPosting) } else { strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting) @@ -3188,7 +3191,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.presentScheduleTimePicker(completion: { [weak self] time in if let strongSelf = self { - if let _ = strongSelf.presentationInterfaceState.recordedMediaPreview { + if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState { strongSelf.sendMediaRecording(scheduleTime: time) } else { strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: time) { [weak self] in @@ -4488,6 +4491,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.canReadHistory.set(true) } self.presentInGlobalOverlay(contextController) + }, openGroupBoostInfo: { [weak self] userId, count in + guard let self, let peerId = self.chatLocation.peerId else { + return + } + let _ = combineLatest(queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { [weak self] boostStatus, myBoostStatus in + guard let self, let boostStatus, let myBoostStatus else { + return + } + let boostController = PremiumBoostLevelsScreen( + context: self.context, + peerId: peerId, + mode: userId.flatMap { .user(mode: .groupPeer($0, count)) } ?? .user(mode: .current), + status: boostStatus, + myBoostStatus: myBoostStatus + ) + self.push(boostController) + }) }, requestMessageUpdate: { [weak self] id, scroll in if let self { self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll) @@ -6667,6 +6690,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.searchQuerySuggestionState?.1.dispose() self.preloadSavedMessagesChatsDisposable?.dispose() self.recorderDataDisposable.dispose() + self.displaySendWhenOnlineTipDisposable.dispose() } deallocate() } @@ -8269,12 +8293,30 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> delay(4.0, queue: Queue.mainQueue()) )) - if !strongSelf.didDisplaySendWhenOnlineTip { - strongSelf.didDisplaySendWhenOnlineTip = true + if !strongSelf.didDisplayGroupEmojiTip, value { + strongSelf.didDisplayGroupEmojiTip = true + Queue.mainQueue().after(2.0) { - strongSelf.displaySendWhenOnlineTooltip() + strongSelf.displayGroupEmojiTooltip() } } + + if !strongSelf.didDisplaySendWhenOnlineTip, value { + strongSelf.didDisplaySendWhenOnlineTip = true + + strongSelf.displaySendWhenOnlineTipDisposable.set( + (strongSelf.typingActivityPromise.get() + |> filter { !$0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] _ in + if let strongSelf = self { + Queue.mainQueue().after(2.0) { + strongSelf.displaySendWhenOnlineTooltip() + } + } + }) + ) + } } else { strongSelf.typingActivityPromise.set(.single(false)) } @@ -10492,6 +10534,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let slowmodeState = strongSelf.presentationInterfaceState.slowmodeState else { return } + + if let boostsToUnrestrict = (strongSelf.peerView?.cachedData as? CachedChannelData)?.boostsToUnrestrict, boostsToUnrestrict > 0 { + strongSelf.interfaceInteraction?.openBoostToUnrestrict() + return + } + let rect = sourceView.convert(nodeRect, to: strongSelf.view) if let slowmodeTooltipController = strongSelf.slowmodeTooltipController { if let arguments = slowmodeTooltipController.presentationArguments as? TooltipControllerPresentationArguments, case let .node(f) = arguments.sourceAndRect, let (previousNode, previousRect) = f() { @@ -10965,6 +11013,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controller = PremiumIntroScreen(context: self.context, source: .settings) self.push(controller) + }, openBoostToUnrestrict: { [weak self] in + guard let self, let peerId = self.chatLocation.peerId, let cachedData = self.peerView?.cachedData as? CachedChannelData, let boostToUnrestrict = cachedData.boostsToUnrestrict else { + return + } + let _ = combineLatest(queue: Queue.mainQueue(), + context.engine.peers.getChannelBoostStatus(peerId: peerId), + context.engine.peers.getMyBoostStatus() + ).startStandalone(next: { [weak self] boostStatus, myBoostStatus in + guard let self, let boostStatus, let myBoostStatus else { + return + } + let boostController = PremiumBoostLevelsScreen( + context: self.context, + peerId: peerId, + mode: .user(mode: .unrestrict(Int(boostToUnrestrict))), + status: boostStatus, + myBoostStatus: myBoostStatus + ) + self.push(boostController) + }) }, updateHistoryFilter: { [weak self] update in guard let self else { return @@ -12833,112 +12901,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - func getCaptionPanelView() -> TGCaptionPanelView? { - var isScheduledMessages = false - if case .scheduledMessages = self.presentationInterfaceState.subject { - isScheduledMessages = true - } - return self.context.sharedContext.makeGalleryCaptionPanelView(context: self.context, chatLocation: self.presentationInterfaceState.chatLocation, isScheduledMessages: isScheduledMessages, customEmojiAvailable: self.presentationInterfaceState.customEmojiAvailable, present: { [weak self] c in - self?.present(c, in: .window(.root)) - }, presentInGlobalOverlay: { [weak self] c in - guard let self else { - return - } - self.presentInGlobalOverlay(c) - }) as? TGCaptionPanelView - } - - func openCamera(cameraView: TGAttachmentCameraView? = nil) { - guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { - return - } - let _ = peer - - let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in - let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) - return entry ?? GeneratedMediaStoreSettings.defaultSettings - } - |> deliverOnMainQueue).startStandalone(next: { [weak self] settings in - guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { - return - } - - var enablePhoto = true - var enableVideo = true - - if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall { - enableVideo = false - } - - var bannedSendPhotos: (Int32, Bool)? - var bannedSendVideos: (Int32, Bool)? - - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = value - } - if let value = channel.hasBannedPermission(.banSendVideos) { - bannedSendVideos = value - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendVideos) { - bannedSendVideos = (Int32.max, false) - } - } - - if bannedSendPhotos != nil { - enablePhoto = false - } - if bannedSendVideos != nil { - enableVideo = false - } - - let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat - let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - - presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in - if let strongSelf = self { - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) - if !inputText.string.isEmpty { - strongSelf.clearInputText() - } - } - }, recognizedQRCode: { [weak self] code in - if let strongSelf = self { - if let (host, port, username, password, secret) = parseProxyUrl(code) { - strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil) - } - } - }, presentSchedulePicker: { [weak self] _, done in - if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in - if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { - strongSelf.openScheduledMessages() - } - } - }) - } - }, presentTimerPicker: { [weak self] done in - if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { time in - done(time) - }) - } - }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() - }, dismissedWithResult: { [weak self] in - self?.attachmentController?.dismiss(animated: false, completion: nil) - }, finishedTransitionIn: { [weak self] in - self?.attachmentController?.scrollToTop?() - }) - }) - } - public func presentAttachmentBot(botId: PeerId, payload: String?, justInstalled: Bool) { self.attachmentController?.dismiss(animated: true, completion: nil) self.presentAttachmentMenu(subject: .bot(id: botId, payload: payload, justInstalled: justInstalled)) @@ -13103,1501 +13065,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - enum AttachMenuSubject { - case `default` - case edit(mediaOptions: MessageMediaEditingOptions, mediaReference: AnyMediaReference) - case bot(id: PeerId, payload: String?, justInstalled: Bool) - case gift - } - - func presentAttachmentMenu(subject: AttachMenuSubject) { - guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { - return - } - - let context = self.context - - let inputIsActive = self.presentationInterfaceState.inputMode == .text - - self.chatDisplayNode.dismissInput() - - var banSendText: (Int32, Bool)? - var bannedSendPhotos: (Int32, Bool)? - var bannedSendVideos: (Int32, Bool)? - var bannedSendFiles: (Int32, Bool)? - - var canSendPolls = true - if let peer = peer as? TelegramUser, peer.botInfo == nil { - canSendPolls = false - } else if peer is TelegramSecretChat { - canSendPolls = false - } else if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = value - } - if let value = channel.hasBannedPermission(.banSendVideos) { - bannedSendVideos = value - } - if let value = channel.hasBannedPermission(.banSendFiles) { - bannedSendFiles = value - } - if let value = channel.hasBannedPermission(.banSendText) { - banSendText = value - } - if channel.hasBannedPermission(.banSendPolls) != nil { - canSendPolls = false - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendVideos) { - bannedSendVideos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendFiles) { - bannedSendFiles = (Int32.max, false) - } - if group.hasBannedPermission(.banSendText) { - banSendText = (Int32.max, false) - } - if group.hasBannedPermission(.banSendPolls) { - canSendPolls = false - } - } - - var availableButtons: [AttachmentButtonType] = [.gallery, .file] - if banSendText == nil { - availableButtons.append(.location) - availableButtons.append(.contact) - } - if canSendPolls { - availableButtons.insert(.poll, at: max(0, availableButtons.count - 1)) - } - - let presentationData = self.presentationData - - var isScheduledMessages = false - if case .scheduledMessages = self.presentationInterfaceState.subject { - isScheduledMessages = true - } - - var peerType: AttachMenuBots.Bot.PeerFlags = [] - if let user = peer as? TelegramUser { - if let _ = user.botInfo { - peerType.insert(.bot) - } else { - peerType.insert(.user) - } - } else if let _ = peer as? TelegramGroup { - peerType = .group - } else if let channel = peer as? TelegramChannel { - if case .broadcast = channel.info { - peerType = .channel - } else { - peerType = .group - } - } - - let buttons: Signal<([AttachmentButtonType], [AttachmentButtonType], AttachmentButtonType?), NoError> - if !isScheduledMessages && !peer.isDeleted { - buttons = self.context.engine.messages.attachMenuBots() - |> map { attachMenuBots in - var buttons = availableButtons - var allButtons = availableButtons - var initialButton: AttachmentButtonType? - switch subject { - case .default: - initialButton = .gallery - case .edit: - break - case .gift: - initialButton = .gift - default: - break - } - - for bot in attachMenuBots.reversed() { - var peerType = peerType - if bot.peer.id == peer.id { - peerType.insert(.sameBot) - peerType.remove(.bot) - } - let button: AttachmentButtonType = .app(bot) - if !bot.peerTypes.intersection(peerType).isEmpty { - buttons.insert(button, at: 1) - - if case let .bot(botId, _, _) = subject { - if initialButton == nil && bot.peer.id == botId { - initialButton = button - } - } - } - allButtons.insert(button, at: 1) - } - - return (buttons, allButtons, initialButton) - } - } else { - buttons = .single((availableButtons, availableButtons, .gallery)) - } - - let dataSettings = self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in - let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) - return entry ?? GeneratedMediaStoreSettings.defaultSettings - } - - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) - let premiumGiftOptions: [CachedPremiumGiftOption] - if !premiumConfiguration.isPremiumDisabled && premiumConfiguration.showPremiumGiftInAttachMenu, let user = peer as? TelegramUser, !user.isPremium && !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) { - premiumGiftOptions = self.presentationInterfaceState.premiumGiftOptions - } else { - premiumGiftOptions = [] - } - - let _ = combineLatest(queue: Queue.mainQueue(), buttons, dataSettings).startStandalone(next: { [weak self] buttonsAndInitialButton, dataSettings in - guard let strongSelf = self else { - return - } - - var (buttons, allButtons, initialButton) = buttonsAndInitialButton - if !premiumGiftOptions.isEmpty { - buttons.insert(.gift, at: 1) - } - - guard let initialButton = initialButton else { - if case let .bot(botId, botPayload, botJustInstalled) = subject { - if let button = allButtons.first(where: { button in - if case let .app(bot) = button, bot.peer.id == botId { - return true - } else { - return false - } - }), case let .app(bot) = button { - let content: UndoOverlayContent - if botJustInstalled { - if bot.flags.contains(.showInSettings) { - content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string, timeout: 5.0, customUndoText: nil) - } else { - content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string, timeout: 5.0, customUndoText: nil) - } - } else { - content = .info(title: nil, text: strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError, timeout: nil, customUndoText: nil) - } - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) - } else { - let _ = (context.engine.messages.getAttachMenuBot(botId: botId) - |> deliverOnMainQueue).startStandalone(next: { bot in - let controller = webAppTermsAlertController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, bot: bot, completion: { allowWrite in - let _ = (context.engine.messages.addBotToAttachMenu(botId: botId, allowWrite: allowWrite) - |> deliverOnMainQueue).startStandalone(error: { _ in - - }, completed: { - strongSelf.presentAttachmentBot(botId: botId, payload: botPayload, justInstalled: true) - }) - }) - strongSelf.present(controller, in: .window(.root)) - }, error: { _ in - strongSelf.present(textAlertController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - }) - } - } - return - } - - let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - - let currentMediaController = Atomic(value: nil) - let currentFilesController = Atomic(value: nil) - let currentLocationController = Atomic(value: nil) - - strongSelf.canReadHistory.set(false) - - let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: strongSelf.chatLocation, isScheduledMessages: isScheduledMessages, buttons: buttons, initialButton: initialButton, makeEntityInputView: { [weak self] in - guard let strongSelf = self else { - return nil - } - return EntityInputView(context: strongSelf.context, isDark: false, areCustomEmojiEnabled: strongSelf.presentationInterfaceState.customEmojiAvailable) - }) - attachmentController.didDismiss = { [weak self] in - self?.attachmentController = nil - self?.canReadHistory.set(true) - } - attachmentController.getSourceRect = { [weak self] in - if let strongSelf = self { - return strongSelf.chatDisplayNode.frameForAttachmentButton()?.offsetBy(dx: strongSelf.chatDisplayNode.supernode?.frame.minX ?? 0.0, dy: 0.0) - } else { - return nil - } - } - attachmentController.requestController = { [weak self, weak attachmentController] type, completion in - guard let strongSelf = self else { - return - } - switch type { - case .gallery: - strongSelf.controllerNavigationDisposable.set(nil) - let existingController = currentMediaController.with { $0 } - if let controller = existingController { - completion(controller, controller.mediaPickerContext) - controller.prepareForReuse() - return - } - strongSelf.presentMediaPicker(saveEditedPhotos: dataSettings.storeEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: { controller, mediaPickerContext in - let _ = currentMediaController.swap(controller) - if !inputText.string.isEmpty { - mediaPickerContext?.setCaption(inputText) - } - completion(controller, mediaPickerContext) - }, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in - attachmentController?.mediaPickerContext = mediaPickerContext - }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in - if !inputText.string.isEmpty { - self?.clearInputText() - } - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) - }) - case .file: - strongSelf.controllerNavigationDisposable.set(nil) - let existingController = currentFilesController.with { $0 } - if let controller = existingController { - completion(controller, controller.mediaPickerContext) - controller.prepareForReuse() - return - } - let controller = strongSelf.context.sharedContext.makeAttachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bannedSendMedia: bannedSendFiles, presentGallery: { [weak self, weak attachmentController] in - attachmentController?.dismiss(animated: true) - self?.presentFileGallery() - }, presentFiles: { [weak self, weak attachmentController] in - attachmentController?.dismiss(animated: true) - self?.presentICloudFileGallery() - }, send: { [weak self] mediaReference in - guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else { - return - } - let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages([message])) - |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in - if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages { - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() - } - }) - }) - if let controller = controller as? AttachmentFileControllerImpl { - let _ = currentFilesController.swap(controller) - completion(controller, controller.mediaPickerContext) - } - case .location: - strongSelf.controllerNavigationDisposable.set(nil) - let existingController = currentLocationController.with { $0 } - if let controller = existingController { - completion(controller, controller.mediaPickerContext) - controller.prepareForReuse() - return - } - let selfPeerId: PeerId - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - selfPeerId = peer.id - } else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) { - selfPeerId = peer.id - } else { - selfPeerId = strongSelf.context.account.peerId - } - let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: selfPeerId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] selfPeer in - guard let strongSelf = self, let selfPeer = selfPeer else { - return - } - let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages - let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _, _ in - guard let strongSelf = self else { - return - } - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - strongSelf.sendMessages([message]) - }) - completion(controller, controller.mediaPickerContext) - - let _ = currentLocationController.swap(controller) - }) - case .contact: - let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true, requirePhoneNumbers: true)) - contactsController.presentScheduleTimePicker = { [weak self] completion in - if let strongSelf = self { - strongSelf.presentScheduleTimePicker(completion: completion) - } - } - contactsController.navigationPresentation = .modal - completion(contactsController, contactsController.mediaPickerContext) - strongSelf.controllerNavigationDisposable.set((contactsController.result - |> deliverOnMainQueue).startStrict(next: { [weak self] peers in - if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers { - var textEnqueueMessage: EnqueueMessage? - if let text = text, text.length > 0 { - var attributes: [MessageAttribute] = [] - let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) - if !entities.isEmpty { - attributes.append(TextEntitiesMessageAttribute(entities: entities)) - } - textEnqueueMessage = .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - } - if peers.count > 1 { - var enqueueMessages: [EnqueueMessage] = [] - if let textEnqueueMessage = textEnqueueMessage { - enqueueMessages.append(textEnqueueMessage) - } - for peer in peers { - var media: TelegramMediaContact? - switch peer { - case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { - continue - } - let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - - let phone = contactData.basicData.phoneNumbers[0].value - media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil) - case let .deviceContact(_, basicData): - guard !basicData.phoneNumbers.isEmpty else { - continue - } - let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - - let phone = contactData.basicData.phoneNumbers[0].value - media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil) - } - - if let media = media { - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - enqueueMessages.append(message) - } - } - strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) - } else if let peer = peers.first { - let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> - switch peer { - case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { - return - } - let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - let context = strongSelf.context - dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:])) - |> take(1) - |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in - var stableId: String? - let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) - outer: for (id, data) in basicData { - for phoneNumber in data.phoneNumbers { - if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber { - stableId = id - break outer - } - } - } - - if let stableId = stableId { - return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) - |> take(1) - |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in - return (contact, extendedData) - } - } else { - return .single((contact, contactData)) - } - } - case let .deviceContact(id, _): - dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil)) - |> take(1) - |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in - return (nil, extendedData) - } - } - strongSelf.controllerNavigationDisposable.set((dataSignal - |> deliverOnMainQueue).startStrict(next: { peerAndContactData in - if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 { - if contactData.isPrimitive { - let phone = contactData.basicData.phoneNumbers[0].value - let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil) - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - - var enqueueMessages: [EnqueueMessage] = [] - if let textEnqueueMessage = textEnqueueMessage { - enqueueMessages.append(textEnqueueMessage) - } - enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) - strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) - } else { - let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in - guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { - return - } - let phone = contactData.basicData.phoneNumbers[0].value - if let vCardData = contactData.serializedVCard() { - let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData) - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - - var enqueueMessages: [EnqueueMessage] = [] - if let textEnqueueMessage = textEnqueueMessage { - enqueueMessages.append(textEnqueueMessage) - } - enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) - strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) - } - }), completed: nil, cancelled: nil) - strongSelf.effectiveNavigationController?.pushViewController(contactController) - } - } - })) - } - } - })) - case .poll: - let controller = strongSelf.configurePollCreation() - completion(controller, controller?.mediaPickerContext) - strongSelf.controllerNavigationDisposable.set(nil) - case .gift: - let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions - if !premiumGiftOptions.isEmpty { - let controller = PremiumGiftAttachmentScreen(context: context, peerIds: [peer.id], options: premiumGiftOptions, source: .attachMenu, pushController: { [weak self] c in - if let strongSelf = self { - strongSelf.push(c) - } - }, completion: { [weak self] in - if let strongSelf = self { - strongSelf.hintPlayNextOutgoingGift() - strongSelf.attachmentController?.dismiss(animated: true) - } - }) - completion(controller, controller.mediaPickerContext) - strongSelf.controllerNavigationDisposable.set(nil) - - let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peer.id).startStandalone() - } - case let .app(bot): - var payload: String? - var fromAttachMenu = true - if case let .bot(_, botPayload, _) = subject { - payload = botPayload - fromAttachMenu = false - } - let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false) - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageSubject?.messageId, threadId: strongSelf.chatLocation.threadId) - controller.openUrl = { [weak self] url, concealed, commit in - self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) - } - controller.getNavigationController = { [weak self] in - return self?.effectiveNavigationController - } - controller.completion = { [weak self] in - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() - } - } - completion(controller, controller.mediaPickerContext) - strongSelf.controllerNavigationDisposable.set(nil) - - if bot.flags.contains(.notActivated) { - let alertController = webAppTermsAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bot: bot, completion: { [weak self] allowWrite in - guard let self else { - return - } - if bot.flags.contains(.showInSettingsDisclaimer) { - let _ = self.context.engine.messages.acceptAttachMenuBotDisclaimer(botId: bot.peer.id).startStandalone() - } - let _ = (self.context.engine.messages.addBotToAttachMenu(botId: bot.peer.id, allowWrite: allowWrite) - |> deliverOnMainQueue).startStandalone(error: { _ in - }, completed: { [weak controller] in - controller?.refresh() - }) - }, - dismissed: { - strongSelf.attachmentController?.dismiss(animated: true) - }) - strongSelf.present(alertController, in: .window(.root)) - } - default: - break - } - } - let present = { - attachmentController.navigationPresentation = .flatModal - strongSelf.push(attachmentController) - strongSelf.attachmentController = attachmentController - - if case let .bot(botId, _, botJustInstalled) = subject, botJustInstalled { - if let button = allButtons.first(where: { button in - if case let .app(bot) = button, bot.peer.id == botId { - return true - } else { - return false - } - }), case let .app(bot) = button { - Queue.mainQueue().after(0.3) { - let content: UndoOverlayContent - if bot.flags.contains(.showInSettings) { - content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string, timeout: 5.0, customUndoText: nil) - } else { - content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string, timeout: 5.0, customUndoText: nil) - } - attachmentController.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) - } - } - } - } - - if inputIsActive { - Queue.mainQueue().after(0.15, { - present() - }) - } else { - present() - } - }) - } - - func oldPresentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) { - let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in - let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) - return entry ?? GeneratedMediaStoreSettings.defaultSettings - } - |> deliverOnMainQueue).startStandalone(next: { [weak self] settings in - guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { - return - } - strongSelf.chatDisplayNode.dismissInput() - - var bannedSendMedia: (Int32, Bool)? - var canSendPolls = true - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendMedia) { - bannedSendMedia = value - } - if channel.hasBannedPermission(.banSendPolls) != nil { - canSendPolls = false - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendMedia) { - bannedSendMedia = (Int32.max, false) - } - if group.hasBannedPermission(.banSendPolls) { - canSendPolls = false - } - } - - if editMediaOptions == nil, let (untilDate, personal) = bannedSendMedia { - let banDescription: String - if untilDate != 0 && untilDate != Int32.max { - banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedMediaTimed(stringForFullDate(timestamp: untilDate, strings: strongSelf.presentationInterfaceState.strings, dateTimeFormat: strongSelf.presentationInterfaceState.dateTimeFormat)).string - } else if personal { - banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedMedia - } else { - banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedMedia - } - - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - var items: [ActionSheetItem] = [] - items.append(ActionSheetTextItem(title: banDescription)) - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Location, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - self?.presentLocationPicker() - })) - if canSendPolls { - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.AttachmentMenu_Poll, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let controller = self?.configurePollCreation() { - self?.effectiveNavigationController?.pushViewController(controller) - } - })) - } - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Contact, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - self?.presentContactPicker() - })) - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.present(actionSheet, in: .window(.root)) - - return - } - - let legacyController = LegacyController(presentation: .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) - legacyController.blocksBackgroundWhenInOverlay = true - legacyController.acceptsFocusWhenInOverlay = true - legacyController.statusBar.statusBarStyle = .Ignore - legacyController.controllerLoaded = { [weak legacyController] in - legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true - } - - let emptyController = LegacyEmptyController(context: legacyController.context)! - let navigationController = makeLegacyNavigationController(rootController: emptyController) - navigationController.setNavigationBarHidden(true, animated: false) - legacyController.bind(controller: navigationController) - - legacyController.enableSizeClassSignal = true - - let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - let menuEditMediaOptions = editMediaOptions.flatMap { options -> LegacyAttachmentMenuMediaEditing in - var result: LegacyAttachmentMenuMediaEditing = .none - if options.contains(.imageOrVideo) { - result = .imageOrVideo(editMediaReference) - } - return result - } - - var slowModeEnabled = false - if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode { - slowModeEnabled = true - } - - let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText, openGallery: { - self?.presentOldMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting, scheduleTime in - if !inputText.string.isEmpty { - strongSelf.clearInputText() - } - if editMediaOptions != nil { - self?.editMessageMediaWithLegacySignals(signals) - } else { - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) - } - }) - }, openCamera: { [weak self] cameraView, menuController in - if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { - var enablePhoto = true - var enableVideo = true - - if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall { - enableVideo = false - } - - var bannedSendPhotos: (Int32, Bool)? - var bannedSendVideos: (Int32, Bool)? - - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = value - } - if let value = channel.hasBannedPermission(.banSendVideos) { - bannedSendVideos = value - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendVideos) { - bannedSendVideos = (Int32.max, false) - } - } - - if bannedSendPhotos != nil { - enablePhoto = false - } - if bannedSendVideos != nil { - enableVideo = false - } - - presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: peer.id.namespace != Namespaces.Peer.SecretChat, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in - if let strongSelf = self { - if editMediaOptions != nil { - strongSelf.editMessageMediaWithLegacySignals(signals!) - } else { - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) - } - if !inputText.string.isEmpty { - strongSelf.clearInputText() - } - } - }, recognizedQRCode: { [weak self] code in - if let strongSelf = self { - if let (host, port, username, password, secret) = parseProxyUrl(code) { - strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil) - } - } - }, presentSchedulePicker: { [weak self] _, done in - if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in - if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { - strongSelf.openScheduledMessages() - } - } - }) - } - }, presentTimerPicker: { [weak self] done in - if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { time in - done(time) - }) - } - }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() - }) - } - }, openFileGallery: { - self?.presentFileMediaPickerOptions(editingMessage: editMediaOptions != nil) - }, openWebSearch: { [weak self] in - self?.presentWebSearch(editingMessage: editMediaOptions != nil, attachment: false, present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a) - }) - }, openMap: { - self?.presentLocationPicker() - }, openContacts: { - self?.presentContactPicker() - }, openPoll: { - if let controller = self?.configurePollCreation() { - self?.effectiveNavigationController?.pushViewController(controller) - } - }, presentSelectionLimitExceeded: { - guard let strongSelf = self else { - return - } - let text: String - if slowModeEnabled { - text = strongSelf.presentationData.strings.Chat_SlowmodeAttachmentLimitReached - } else { - text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached - } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - }, presentCantSendMultipleFiles: { - guard let strongSelf = self else { - return - } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_AttachmentMultipleFilesDisabled, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - }, presentJpegConversionAlert: { completion in - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.MediaPicker_KeepHeic, action: { - completion(false) - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.MediaPicker_ConvertToJpeg, action: { - completion(true) - })], actionLayout: .vertical), in: .window(.root)) - }, presentSchedulePicker: { [weak self] _, done in - if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in - if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { - strongSelf.openScheduledMessages() - } - } - }) - } - }, presentTimerPicker: { [weak self] done in - if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { time in - done(time) - }) - } - }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in - guard let strongSelf = self else { - completion() - return - } - if !inputText.string.isEmpty { - strongSelf.clearInputText() - } - if editMediaOptions != nil { - strongSelf.editMessageMediaWithLegacySignals(signals!) - completion() - } else { - let immediateCompletion = getAnimatedTransitionSource == nil - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: { - if !immediateCompletion { - completion() - } - }) - if immediateCompletion { - completion() - } - } - }, selectRecentlyUsedInlineBot: { [weak self] peer in - if let strongSelf = self, let addressName = peer.addressName { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState({ $0.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: "@" + addressName + " "))) }).updatedInputMode({ _ in - return .text - }) - }) - } - }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() - }, present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a) - }) - controller.didDismiss = { [weak legacyController] _ in - legacyController?.dismiss() - } - controller.customRemoveFromParentViewController = { [weak legacyController] in - legacyController?.dismiss() - } - - legacyController.blocksBackgroundWhenInOverlay = true - strongSelf.present(legacyController, in: .window(.root)) - controller.present(in: emptyController, sourceView: nil, animated: true) - - let presentationDisposable = strongSelf.updatedPresentationData.1.startStrict(next: { [weak controller] presentationData in - if let controller = controller { - controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme, forceDark: false) - } - }) - legacyController.disposables.add(presentationDisposable) - }) - } - - func presentFileGallery(editingMessage: Bool = false) { - self.presentOldMediaPicker(fileMode: true, editingMedia: editingMessage, completion: { [weak self] signals, silentPosting, scheduleTime in - if editingMessage { - self?.editMessageMediaWithLegacySignals(signals) - } else { - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) - } - }) - } - - func presentICloudFileGallery(editingMessage: Bool = false) { - let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) - ) - |> deliverOnMainQueue).startStandalone(next: { [weak self] result in - guard let strongSelf = self else { - return - } - let (accountPeer, limits, premiumLimits) = result - let isPremium = accountPeer?.isPremium ?? false - - strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, completion: { [weak self] urls in - if let strongSelf = self, !urls.isEmpty { - var signals: [Signal] = [] - for url in urls { - signals.append(iCloudFileDescription(url)) - } - strongSelf.enqueueMediaMessageDisposable.set((combineLatest(signals) - |> deliverOnMainQueue).startStrict(next: { results in - if let strongSelf = self { - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - - for item in results { - if let item = item { - if item.fileSize > Int64(premiumLimits.maxUploadFileParts) * 512 * 1024 { - let controller = PremiumLimitScreen(context: strongSelf.context, subject: .files, count: 4, action: { - return true - }) - strongSelf.push(controller) - return - } else if item.fileSize > Int64(limits.maxUploadFileParts) * 512 * 1024 && !isPremium { - let context = strongSelf.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .files, count: 2, action: { - replaceImpl?(PremiumIntroScreen(context: context, source: .upload)) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - return - } - } - } - - var groupingKey: Int64? - var fileTypes: (music: Bool, other: Bool) = (false, false) - if results.count > 1 { - for item in results { - if let item = item { - let pathExtension = (item.fileName as NSString).pathExtension.lowercased() - if ["mp3", "m4a"].contains(pathExtension) { - fileTypes.music = true - } else { - fileTypes.other = true - } - } - } - } - if fileTypes.music != fileTypes.other { - groupingKey = Int64.random(in: Int64.min ... Int64.max) - } - - var messages: [EnqueueMessage] = [] - for item in results { - if let item = item { - let fileId = Int64.random(in: Int64.min ... Int64.max) - let mimeType = guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension) - var previewRepresentations: [TelegramMediaImageRepresentation] = [] - if mimeType.hasPrefix("image/") || mimeType == "application/pdf" { - previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 320, height: 320), resource: ICloudFileResource(urlData: item.urlData, thumbnail: true), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - } - var attributes: [TelegramMediaFileAttribute] = [] - attributes.append(.FileName(fileName: item.fileName)) - if let audioMetadata = item.audioMetadata { - attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil)) - } - - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes) - let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []) - messages.append(message) - } - if let _ = groupingKey, messages.count % 10 == 0 { - groupingKey = Int64.random(in: Int64.min ... Int64.max) - } - } - - if !messages.isEmpty { - if editingMessage { - strongSelf.editMessageMediaWithMessages(messages) - } else { - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - strongSelf.sendMessages(messages) - } - } - } - })) - } - }), in: .window(.root)) - }) - } - - func presentFileMediaPickerOptions(editingMessage: Bool) { - let actionSheet = ActionSheetController(presentationData: self.presentationData) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Conversation_FilePhotoOrVideo, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.presentFileGallery(editingMessage: editingMessage) - } - }), - ActionSheetButtonItem(title: self.presentationData.strings.Conversation_FileICloudDrive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.presentICloudFileGallery(editingMessage: editingMessage) - } - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - self.chatDisplayNode.dismissInput() - self.present(actionSheet, in: .window(.root)) - } - - func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { - guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { - return - } - var isScheduledMessages = false - if case .scheduledMessages = self.presentationInterfaceState.subject { - isScheduledMessages = true - } - let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), threadTitle: self.threadInfo?.title, chatLocation: self.chatLocation, isScheduledMessages: isScheduledMessages, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, subject: subject, saveEditedPhotos: saveEditedPhotos) - let mediaPickerContext = controller.mediaPickerContext - controller.openCamera = { [weak self] cameraView in - self?.openCamera(cameraView: cameraView) - } - controller.presentWebSearch = { [weak self, weak controller] mediaGroups, activateOnDisplay in - self?.presentWebSearch(editingMessage: false, attachment: true, activateOnDisplay: activateOnDisplay, present: { [weak controller] c, a in - controller?.present(c, in: .current) - if let webSearchController = c as? WebSearchController { - webSearchController.searchingUpdated = { [weak mediaGroups] searching in - if let mediaGroups = mediaGroups, mediaGroups.isNodeLoaded { - let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) - transition.updateAlpha(node: mediaGroups.displayNode, alpha: searching ? 0.0 : 1.0) - mediaGroups.displayNode.isUserInteractionEnabled = !searching - } - } - webSearchController.present(mediaGroups, in: .current) - webSearchController.dismissed = { - updateMediaPickerContext(mediaPickerContext) - } - controller?.webSearchController = webSearchController - updateMediaPickerContext(webSearchController.mediaPickerContext) - } - }) - } - controller.presentSchedulePicker = { [weak self] media, done in - if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time in - if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { - strongSelf.openScheduledMessages() - } - } - }) - } - } - controller.presentTimerPicker = { [weak self] done in - if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { time in - done(time) - }) - } - } - controller.getCaptionPanelView = { [weak self] in - return self?.getCaptionPanelView() - } - controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in - completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion) - } - present(controller, mediaPickerContext) - } - - func presentOldMediaPicker(fileMode: Bool, editingMedia: Bool, completion: @escaping ([Any], Bool, Int32) -> Void) { - let engine = self.context.engine - let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Signal<(GeneratedMediaStoreSettings, EngineConfiguration.SearchBots), NoError> in - let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) - - return engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) - |> map { configuration -> (GeneratedMediaStoreSettings, EngineConfiguration.SearchBots) in - return (entry ?? GeneratedMediaStoreSettings.defaultSettings, configuration) - } - } - |> switchToLatest - |> deliverOnMainQueue).startStandalone(next: { [weak self] settings, searchBotsConfiguration in - guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { - return - } - let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - var selectionLimit: Int = 100 - var slowModeEnabled = false - if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode { - selectionLimit = 10 - slowModeEnabled = true - } - - let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, threadTitle: strongSelf.threadInfo?.title, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, selectionLimit: selectionLimit).startStandalone(next: { generator in - if let strongSelf = self { - let legacyController = LegacyController(presentation: fileMode ? .navigation : .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) - legacyController.navigationPresentation = .modal - legacyController.statusBar.statusBarStyle = strongSelf.presentationData.theme.rootController.statusBarStyle.style - legacyController.controllerLoaded = { [weak legacyController] in - legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true - legacyController?.view.disablesInteractiveModalDismiss = true - } - let controller = generator(legacyController.context) - - legacyController.bind(controller: controller) - legacyController.deferScreenEdgeGestures = [.top] - - configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: editingMedia ? nil : { [weak self, weak legacyController] in - if let strongSelf = self { - let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), chatLocation: strongSelf.chatLocation, configuration: searchBotsConfiguration, mode: .media(attachment: false, completion: { results, selectionState, editingState, silentPosting in - if let legacyController = legacyController { - legacyController.dismiss() - } - legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { result in - if let strongSelf = self { - strongSelf.enqueueChatContextResult(results, result, hideVia: true) - } - }, enqueueMediaMessages: { signals in - if let strongSelf = self { - if editingMedia { - strongSelf.editMessageMediaWithLegacySignals(signals) - } else { - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) - } - } - }) - })) - controller.getCaptionPanelView = { [weak self] in - return self?.getCaptionPanelView() - } - strongSelf.effectiveNavigationController?.pushViewController(controller) - } - }, presentSelectionLimitExceeded: { - guard let strongSelf = self else { - return - } - - let text: String - if slowModeEnabled { - text = strongSelf.presentationData.strings.Chat_SlowmodeAttachmentLimitReached - } else { - text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached - } - - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - }, presentSchedulePicker: { [weak self] media, done in - if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time in - if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { - strongSelf.openScheduledMessages() - } - } - }) - } - }, presentTimerPicker: { [weak self] done in - if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { time in - done(time) - }) - } - }, getCaptionPanelView: { [weak self] in - return self?.getCaptionPanelView() - }) - controller.descriptionGenerator = legacyAssetPickerItemGenerator() - controller.completionBlock = { [weak legacyController] signals, silentPosting, scheduleTime in - if let legacyController = legacyController { - legacyController.dismiss(animated: true) - completion(signals!, silentPosting, scheduleTime) - } - } - controller.dismissalBlock = { [weak legacyController] in - if let legacyController = legacyController { - legacyController.dismiss(animated: true) - } - } - strongSelf.chatDisplayNode.dismissInput() - strongSelf.effectiveNavigationController?.pushViewController(legacyController) - } - }) - }) - } - - func presentWebSearch(editingMessage: Bool, attachment: Bool, activateOnDisplay: Bool = true, present: @escaping (ViewController, Any?) -> Void) { - guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { - return - } - - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) - |> deliverOnMainQueue).startStandalone(next: { [weak self] configuration in - if let strongSelf = self { - let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), chatLocation: strongSelf.chatLocation, configuration: configuration, mode: .media(attachment: attachment, completion: { [weak self] results, selectionState, editingState, silentPosting in - self?.attachmentController?.dismiss(animated: true, completion: nil) - legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak self] result in - if let strongSelf = self { - strongSelf.enqueueChatContextResult(results, result, hideVia: true) - } - }, enqueueMediaMessages: { [weak self] signals in - if let strongSelf = self, !signals.isEmpty { - if editingMessage { - strongSelf.editMessageMediaWithLegacySignals(signals) - } else { - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) - } - } - }) - }), activateOnDisplay: activateOnDisplay) - controller.attemptItemSelection = { [weak strongSelf] item in - guard let strongSelf, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { - return false - } - - enum ItemType { - case gif - case image - case video - } - - var itemType: ItemType? - switch item { - case let .internalReference(reference): - if reference.type == "gif" { - itemType = .gif - } else if reference.type == "photo" { - itemType = .image - } else if reference.type == "video" { - itemType = .video - } - case let .externalReference(reference): - if reference.type == "gif" { - itemType = .gif - } else if reference.type == "photo" { - itemType = .image - } else if reference.type == "video" { - itemType = .video - } - } - - var bannedSendPhotos: (Int32, Bool)? - var bannedSendVideos: (Int32, Bool)? - var bannedSendGifs: (Int32, Bool)? - - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = value - } - if let value = channel.hasBannedPermission(.banSendVideos) { - bannedSendVideos = value - } - if let value = channel.hasBannedPermission(.banSendGifs) { - bannedSendGifs = value - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendVideos) { - bannedSendVideos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendGifs) { - bannedSendGifs = (Int32.max, false) - } - } - - if let itemType { - switch itemType { - case .image: - if bannedSendPhotos != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - case .video: - if bannedSendVideos != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - case .gif: - if bannedSendGifs != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - } - } - - return true - } - controller.getCaptionPanelView = { [weak strongSelf] in - return strongSelf?.getCaptionPanelView() - } - present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } - }) - } - - func presentLocationPicker() { - guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { - return - } - let selfPeerId: PeerId - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - selfPeerId = peer.id - } else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) { - selfPeerId = peer.id - } else { - selfPeerId = self.context.account.peerId - } - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: selfPeerId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] selfPeer in - guard let strongSelf = self, let selfPeer = selfPeer else { - return - } - let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages - let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _, _ in - guard let strongSelf = self else { - return - } - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - strongSelf.sendMessages([message]) - }) - strongSelf.effectiveNavigationController?.pushViewController(controller) - strongSelf.chatDisplayNode.dismissInput() - }) - } - - func presentContactPicker() { - let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true)) - contactsController.navigationPresentation = .modal - self.chatDisplayNode.dismissInput() - self.effectiveNavigationController?.pushViewController(contactsController) - self.controllerNavigationDisposable.set((contactsController.result - |> deliverOnMainQueue).startStrict(next: { [weak self] peers in - if let strongSelf = self, let (peers, _, _, _, _) = peers { - if peers.count > 1 { - var enqueueMessages: [EnqueueMessage] = [] - for peer in peers { - var media: TelegramMediaContact? - switch peer { - case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { - continue - } - let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - - let phone = contactData.basicData.phoneNumbers[0].value - media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil) - case let .deviceContact(_, basicData): - guard !basicData.phoneNumbers.isEmpty else { - continue - } - let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - - let phone = contactData.basicData.phoneNumbers[0].value - media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil) - } - - if let media = media { - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - enqueueMessages.append(message) - } - } - strongSelf.sendMessages(enqueueMessages) - } else if let peer = peers.first { - let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> - switch peer { - case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { - return - } - let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - let context = strongSelf.context - dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:])) - |> take(1) - |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in - var stableId: String? - let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) - outer: for (id, data) in basicData { - for phoneNumber in data.phoneNumbers { - if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber { - stableId = id - break outer - } - } - } - - if let stableId = stableId { - return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) - |> take(1) - |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in - return (contact, extendedData) - } - } else { - return .single((contact, contactData)) - } - } - case let .deviceContact(id, _): - dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil)) - |> take(1) - |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in - return (nil, extendedData) - } - } - strongSelf.controllerNavigationDisposable.set((dataSignal - |> deliverOnMainQueue).startStrict(next: { peerAndContactData in - if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 { - if contactData.isPrimitive { - let phone = contactData.basicData.phoneNumbers[0].value - let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil) - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - strongSelf.sendMessages([message]) - } else { - let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in - guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { - return - } - let phone = contactData.basicData.phoneNumbers[0].value - if let vCardData = contactData.serializedVCard() { - let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData) - let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, nil) - let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - strongSelf.sendMessages([message]) - } - }), completed: nil, cancelled: nil) - strongSelf.effectiveNavigationController?.pushViewController(contactController) - } - } - })) - } - } - })) - } - func displayPollSolution(solution: TelegramMediaPollResults.Solution, sourceNode: ASDisplayNode, isAutomatic: Bool) { var maybeFoundItemNode: ChatMessageItemView? self.chatDisplayNode.historyNode.forEachItemNode { itemNode in @@ -15554,6 +14021,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G allowLiveUpload: peerId.namespace != Namespaces.Peer.SecretChat, viewOnceAvailable: !isScheduledMessages && peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId && !isBot, inputPanelFrame: currentInputPanelFrame, + chatNode: self.chatDisplayNode.historyNode, completion: { [weak self] message, silentPosting, scheduleTime in guard let self, let videoController = self.videoRecorderValue else { return @@ -15591,7 +14059,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatDisplayNode.collapseInput() self.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedRecordedMediaPreview(nil).updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedMediaDraftState(nil) } }) } }, usedCorrelationId ? correlationId : nil) @@ -15671,7 +14139,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - $0.updatedRecordedMediaPreview(.audio(ChatRecordedMediaPreview.Audio(resource: resource, fileSize: Int32(data.compressedData.count), duration: Int32(data.duration), waveform: AudioWaveform(bitstream: waveform, bitsPerSample: 5)))).updatedInputTextPanelState { panelState in + $0.updatedInterfaceState { $0.withUpdatedMediaDraftState(.audio(ChatInterfaceMediaDraftState.Audio(resource: resource, fileSize: Int32(data.compressedData.count), duration: Int32(data.duration), waveform: AudioWaveform(bitstream: waveform, bitsPerSample: 5)))) }.updatedInputTextPanelState { panelState in return panelState.withUpdatedMediaRecordingState(nil) } }) @@ -15766,21 +14234,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.videoRecorder.set(.single(nil)) } else { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - $0.updatedRecordedMediaPreview(.video( - ChatRecordedMediaPreview.Video( - duration: Int32(data.duration), - frames: data.frames, - framesUpdateTimestamp: data.framesUpdateTimestamp, - trimRange: data.trimRange, - control: ChatRecordedMediaPreview.Video.Control( - updateTrimRange: { [weak self] start, end, updatedEnd, apply in - if let self, let videoRecorderValue = self.videoRecorderValue { - videoRecorderValue.updateTrimRange(start: start, end: end, updatedEnd: updatedEnd, apply: apply) - } - } + $0.updatedInterfaceState { + $0.withUpdatedMediaDraftState(.video( + ChatInterfaceMediaDraftState.Video( + duration: Int32(data.duration), + frames: data.frames, + framesUpdateTimestamp: data.framesUpdateTimestamp, + trimRange: data.trimRange +// control: ChatRecordedMediaPreview.Video.Control( +// updateTrimRange: { [weak self] start, end, updatedEnd, apply in +// if let self, let videoRecorderValue = self.videoRecorderValue { +// videoRecorderValue.updateTrimRange(start: start, end: end, updatedEnd: updatedEnd, apply: apply) +// } +// } +// ) ) - ) - )).updatedInputTextPanelState { panelState in + )) + }.updatedInputTextPanelState { panelState in return panelState.withUpdatedMediaRecordingState(nil) } }) @@ -15824,14 +14294,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInputTextPanelState { panelState in return panelState.withUpdatedMediaRecordingState(.audio(recorder: audioRecorderValue, isLocked: true)) - }.updatedRecordedMediaPreview(nil) + }.updatedInterfaceState { $0.withUpdatedMediaDraftState(nil) } }) } else if let videoRecorderValue = self.videoRecorderValue { self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInputTextPanelState { panelState in let recordingStatus = videoRecorderValue.recordingStatus return panelState.withUpdatedMediaRecordingState(.video(status: .recording(InstantVideoControllerRecordingStatus(micLevel: recordingStatus.micLevel, duration: recordingStatus.duration)), isLocked: true)) - }.updatedRecordedMediaPreview(nil) + }.updatedInterfaceState { $0.withUpdatedMediaDraftState(nil) } }) } } @@ -15858,7 +14328,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.recorderDataDisposable.set(nil) self.chatDisplayNode.updateRecordedMediaDeleted(true) self.updateChatPresentationInterfaceState(animated: true, interactive: true, { - $0.updatedRecordedMediaPreview(nil) + $0.updatedInterfaceState { $0.withUpdatedMediaDraftState(nil) } }) self.updateDownButtonVisibility() } @@ -15866,7 +14336,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G func sendMediaRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, viewOnce: Bool = false) { self.chatDisplayNode.updateRecordedMediaDeleted(false) - guard let recordedMediaPreview = self.presentationInterfaceState.recordedMediaPreview else { + guard let recordedMediaPreview = self.presentationInterfaceState.interfaceState.mediaDraftState else { return } @@ -15893,7 +14363,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedRecordedMediaPreview(nil).updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedMediaDraftState(nil) } }) strongSelf.updateDownButtonVisibility() @@ -15934,7 +14404,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func updateDownButtonVisibility() { - let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.recordedMediaPreview != nil + let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.interfaceState.mediaDraftState != nil if let search = self.presentationInterfaceState.search, let results = search.resultsState, results.messageIndices.count != 0 { var resultIndex: Int? @@ -17180,6 +15650,67 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } + func displayGroupEmojiTooltip() { + guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else { + return + } + guard let emojiPack = (self.peerView?.cachedData as? CachedChannelData)?.emojiPack, let thumbnailFileId = emojiPack.thumbnailFileId else { + return + } + //TODO:localize + let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [thumbnailFileId]) + |> deliverOnMainQueue).start(next: { files in + guard let emojiFile = files.values.first else { + return + } + + let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) + let boldTextFont = Font.bold(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) + let textColor = UIColor.white + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in + return nil + }) + + let text = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("All members of this group can\nuse the # **\(emojiPack.title)** pack", attributes: markdownAttributes)) + + let range = (text.string as NSString).range(of: "#") + if range.location != NSNotFound { + text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range) + } + + let tooltipScreen = TooltipScreen( + context: self.context, + account: self.context.account, + sharedContext: self.context.sharedContext, + text: .attributedString(text: text), +// style: .customBlur(UIColor(rgb: 0x000000, alpha: 0.8), 2.0), + location: .point(rect.offsetBy(dx: 0.0, dy: -3.0), .bottom), + displayDuration: .default, + cornerRadius: 10.0, + shouldDismissOnTouch: { point, _ in + return .ignore + } + ) + self.present(tooltipScreen, in: .current) + + +// self.emojiTooltipController?.dismiss() +// let tooltipController = TooltipController(content: .attributedText(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 3.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 8.0) +// self.emojiTooltipController = tooltipController +// tooltipController.dismissed = { [weak self, weak tooltipController] _ in +// if let strongSelf = self, let tooltipController = tooltipController, strongSelf.emojiTooltipController === tooltipController { +// strongSelf.emojiTooltipController = nil +// } +// } +// self.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in +// if let strongSelf = self { +// return (strongSelf.chatDisplayNode, rect.offsetBy(dx: 0.0, dy: -3.0)) +// } +// return nil +// })) + }) + } + func displayChecksTooltip() { self.checksTooltipController?.dismiss() @@ -17583,7 +16114,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func presentRecordedVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool { - if let _ = self.presentationInterfaceState.recordedMediaPreview { + if let _ = self.presentationInterfaceState.interfaceState.mediaDraftState { alertAction?() Queue.mainQueue().after(delay ? 0.2 : 0.0) { self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction, action: { [weak self] in diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index dc3c46ea0f..c4bcdfce2a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -274,7 +274,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var isLoadingValue: Bool = false private var isLoadingEarlier: Bool = false private func updateIsLoading(isLoading: Bool, earlier: Bool, animated: Bool) { - var useLoadingPlaceholder = self.chatLocation.peerId?.namespace != Namespaces.Peer.SecretChat + var useLoadingPlaceholder = self.chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser && self.chatLocation.peerId?.namespace != Namespaces.Peer.SecretChat if case let .replyThread(message) = self.chatLocation, message.peerId == self.context.account.peerId { useLoadingPlaceholder = true } @@ -3741,6 +3741,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let effectiveInputText = effectivePresentationInterfaceState.interfaceState.composeInputState.inputText + let peerSpecificEmojiPack = (self.controller?.peerView?.cachedData as? CachedChannelData)?.emojiPack + var inlineStickers: [MediaId: Media] = [:] var firstLockedPremiumEmoji: TelegramMediaFile? var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:] @@ -3751,7 +3753,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let packId = value.interactivelySelectedFromPackId { bubbleUpEmojiOrStickersetsById[file.fileId.id] = packId } - if file.isPremiumEmoji && !self.chatPresentationInterfaceState.isPremium && self.chatPresentationInterfaceState.chatLocation.peerId != self.context.account.peerId { + + var isPeerSpecific = false + for attribute in file.attributes { + if case let .CustomEmoji(_, _, _, packReference) = attribute, case let .id(id, _) = packReference { + isPeerSpecific = id == peerSpecificEmojiPack?.id.id + } + } + + if file.isPremiumEmoji && !self.chatPresentationInterfaceState.isPremium && self.chatPresentationInterfaceState.chatLocation.peerId != self.context.account.peerId && !isPeerSpecific { if firstLockedPremiumEmoji == nil { firstLockedPremiumEmoji = file } @@ -4096,7 +4106,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if self.chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState != nil { return false } - if self.chatPresentationInterfaceState.recordedMediaPreview != nil { + if self.chatPresentationInterfaceState.interfaceState.mediaDraftState != nil { return false } if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift new file mode 100644 index 0000000000..3c9ab0de89 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -0,0 +1,1634 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import TelegramStringFormatting +import TelegramNotices +import PresentationDataUtils +import TextFormat +import UrlHandling +import AccountContext +import ChatPresentationInterfaceState +import LegacyComponents +import LegacyUI +import AttachmentUI +import MediaPickerUI +import LegacyCamera +import LegacyMediaPickerUI +import LocationUI +import WebSearchUI +import WebUI +import UndoUI +import ICloudResources +import PhoneNumberFormat +import ChatEntityKeyboardInputNode +import PremiumUI +import PremiumGiftAttachmentScreen +import TelegramCallsUI + +extension ChatControllerImpl { + enum AttachMenuSubject { + case `default` + case edit(mediaOptions: MessageMediaEditingOptions, mediaReference: AnyMediaReference) + case bot(id: PeerId, payload: String?, justInstalled: Bool) + case gift + } + + func presentAttachmentMenu(subject: AttachMenuSubject) { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return + } + + let context = self.context + + let inputIsActive = self.presentationInterfaceState.inputMode == .text + + self.chatDisplayNode.dismissInput() + + var banSendText: (Int32, Bool)? + var bannedSendPhotos: (Int32, Bool)? + var bannedSendVideos: (Int32, Bool)? + var bannedSendFiles: (Int32, Bool)? + + var canSendPolls = true + if let peer = peer as? TelegramUser, peer.botInfo == nil { + canSendPolls = false + } else if peer is TelegramSecretChat { + canSendPolls = false + } else if let channel = peer as? TelegramChannel { + if let value = channel.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = value + } + if let value = channel.hasBannedPermission(.banSendVideos) { + bannedSendVideos = value + } + if let value = channel.hasBannedPermission(.banSendFiles) { + bannedSendFiles = value + } + if let value = channel.hasBannedPermission(.banSendText) { + banSendText = value + } + if channel.hasBannedPermission(.banSendPolls) != nil { + canSendPolls = false + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = (Int32.max, false) + } + if group.hasBannedPermission(.banSendVideos) { + bannedSendVideos = (Int32.max, false) + } + if group.hasBannedPermission(.banSendFiles) { + bannedSendFiles = (Int32.max, false) + } + if group.hasBannedPermission(.banSendText) { + banSendText = (Int32.max, false) + } + if group.hasBannedPermission(.banSendPolls) { + canSendPolls = false + } + } + + var availableButtons: [AttachmentButtonType] = [.gallery, .file] + if banSendText == nil { + availableButtons.append(.location) + availableButtons.append(.contact) + } + if canSendPolls { + availableButtons.insert(.poll, at: max(0, availableButtons.count - 1)) + } + + let presentationData = self.presentationData + + var isScheduledMessages = false + if case .scheduledMessages = self.presentationInterfaceState.subject { + isScheduledMessages = true + } + + var peerType: AttachMenuBots.Bot.PeerFlags = [] + if let user = peer as? TelegramUser { + if let _ = user.botInfo { + peerType.insert(.bot) + } else { + peerType.insert(.user) + } + } else if let _ = peer as? TelegramGroup { + peerType = .group + } else if let channel = peer as? TelegramChannel { + if case .broadcast = channel.info { + peerType = .channel + } else { + peerType = .group + } + } + + let buttons: Signal<([AttachmentButtonType], [AttachmentButtonType], AttachmentButtonType?), NoError> + if !isScheduledMessages && !peer.isDeleted { + buttons = self.context.engine.messages.attachMenuBots() + |> map { attachMenuBots in + var buttons = availableButtons + var allButtons = availableButtons + var initialButton: AttachmentButtonType? + switch subject { + case .default: + initialButton = .gallery + case .edit: + break + case .gift: + initialButton = .gift + default: + break + } + + for bot in attachMenuBots.reversed() { + var peerType = peerType + if bot.peer.id == peer.id { + peerType.insert(.sameBot) + peerType.remove(.bot) + } + let button: AttachmentButtonType = .app(bot) + if !bot.peerTypes.intersection(peerType).isEmpty { + buttons.insert(button, at: 1) + + if case let .bot(botId, _, _) = subject { + if initialButton == nil && bot.peer.id == botId { + initialButton = button + } + } + } + allButtons.insert(button, at: 1) + } + + return (buttons, allButtons, initialButton) + } + } else { + buttons = .single((availableButtons, availableButtons, .gallery)) + } + + let dataSettings = self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in + let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) + return entry ?? GeneratedMediaStoreSettings.defaultSettings + } + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) + let premiumGiftOptions: [CachedPremiumGiftOption] + if !premiumConfiguration.isPremiumDisabled && premiumConfiguration.showPremiumGiftInAttachMenu, let user = peer as? TelegramUser, !user.isPremium && !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) { + premiumGiftOptions = self.presentationInterfaceState.premiumGiftOptions + } else { + premiumGiftOptions = [] + } + + let _ = combineLatest(queue: Queue.mainQueue(), buttons, dataSettings).startStandalone(next: { [weak self] buttonsAndInitialButton, dataSettings in + guard let strongSelf = self else { + return + } + + var (buttons, allButtons, initialButton) = buttonsAndInitialButton + if !premiumGiftOptions.isEmpty { + buttons.insert(.gift, at: 1) + } + + guard let initialButton = initialButton else { + if case let .bot(botId, botPayload, botJustInstalled) = subject { + if let button = allButtons.first(where: { button in + if case let .app(bot) = button, bot.peer.id == botId { + return true + } else { + return false + } + }), case let .app(bot) = button { + let content: UndoOverlayContent + if botJustInstalled { + if bot.flags.contains(.showInSettings) { + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string, timeout: 5.0, customUndoText: nil) + } else { + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string, timeout: 5.0, customUndoText: nil) + } + } else { + content = .info(title: nil, text: strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError, timeout: nil, customUndoText: nil) + } + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) + } else { + let _ = (context.engine.messages.getAttachMenuBot(botId: botId) + |> deliverOnMainQueue).startStandalone(next: { bot in + let controller = webAppTermsAlertController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, bot: bot, completion: { allowWrite in + let _ = (context.engine.messages.addBotToAttachMenu(botId: botId, allowWrite: allowWrite) + |> deliverOnMainQueue).startStandalone(error: { _ in + + }, completed: { + strongSelf.presentAttachmentBot(botId: botId, payload: botPayload, justInstalled: true) + }) + }) + strongSelf.present(controller, in: .window(.root)) + }, error: { _ in + strongSelf.present(textAlertController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + }) + } + } + return + } + + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText + + let currentMediaController = Atomic(value: nil) + let currentFilesController = Atomic(value: nil) + let currentLocationController = Atomic(value: nil) + + strongSelf.canReadHistory.set(false) + + let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: strongSelf.chatLocation, isScheduledMessages: isScheduledMessages, buttons: buttons, initialButton: initialButton, makeEntityInputView: { [weak self] in + guard let strongSelf = self else { + return nil + } + return EntityInputView(context: strongSelf.context, isDark: false, areCustomEmojiEnabled: strongSelf.presentationInterfaceState.customEmojiAvailable) + }) + attachmentController.didDismiss = { [weak self] in + self?.attachmentController = nil + self?.canReadHistory.set(true) + } + attachmentController.getSourceRect = { [weak self] in + if let strongSelf = self { + return strongSelf.chatDisplayNode.frameForAttachmentButton()?.offsetBy(dx: strongSelf.chatDisplayNode.supernode?.frame.minX ?? 0.0, dy: 0.0) + } else { + return nil + } + } + attachmentController.requestController = { [weak self, weak attachmentController] type, completion in + guard let strongSelf = self else { + return + } + switch type { + case .gallery: + strongSelf.controllerNavigationDisposable.set(nil) + let existingController = currentMediaController.with { $0 } + if let controller = existingController { + completion(controller, controller.mediaPickerContext) + controller.prepareForReuse() + return + } + strongSelf.presentMediaPicker(saveEditedPhotos: dataSettings.storeEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: { controller, mediaPickerContext in + let _ = currentMediaController.swap(controller) + if !inputText.string.isEmpty { + mediaPickerContext?.setCaption(inputText) + } + completion(controller, mediaPickerContext) + }, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in + attachmentController?.mediaPickerContext = mediaPickerContext + }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + if !inputText.string.isEmpty { + self?.clearInputText() + } + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + }) + case .file: + strongSelf.controllerNavigationDisposable.set(nil) + let existingController = currentFilesController.with { $0 } + if let controller = existingController { + completion(controller, controller.mediaPickerContext) + controller.prepareForReuse() + return + } + let controller = strongSelf.context.sharedContext.makeAttachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bannedSendMedia: bannedSendFiles, presentGallery: { [weak self, weak attachmentController] in + attachmentController?.dismiss(animated: true) + self?.presentFileGallery() + }, presentFiles: { [weak self, weak attachmentController] in + attachmentController?.dismiss(animated: true) + self?.presentICloudFileGallery() + }, send: { [weak self] mediaReference in + guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else { + return + } + let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages([message])) + |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in + if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages { + strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() + } + }) + }) + if let controller = controller as? AttachmentFileControllerImpl { + let _ = currentFilesController.swap(controller) + completion(controller, controller.mediaPickerContext) + } + case .location: + strongSelf.controllerNavigationDisposable.set(nil) + let existingController = currentLocationController.with { $0 } + if let controller = existingController { + completion(controller, controller.mediaPickerContext) + controller.prepareForReuse() + return + } + let selfPeerId: PeerId + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + selfPeerId = peer.id + } else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) { + selfPeerId = peer.id + } else { + selfPeerId = strongSelf.context.account.peerId + } + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: selfPeerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] selfPeer in + guard let strongSelf = self, let selfPeer = selfPeer else { + return + } + let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages + let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _, _ in + guard let strongSelf = self else { + return + } + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + strongSelf.sendMessages([message]) + }) + completion(controller, controller.mediaPickerContext) + + let _ = currentLocationController.swap(controller) + }) + case .contact: + let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true, requirePhoneNumbers: true)) + contactsController.presentScheduleTimePicker = { [weak self] completion in + if let strongSelf = self { + strongSelf.presentScheduleTimePicker(completion: completion) + } + } + contactsController.navigationPresentation = .modal + completion(contactsController, contactsController.mediaPickerContext) + strongSelf.controllerNavigationDisposable.set((contactsController.result + |> deliverOnMainQueue).startStrict(next: { [weak self] peers in + if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers { + var textEnqueueMessage: EnqueueMessage? + if let text = text, text.length > 0 { + var attributes: [MessageAttribute] = [] + let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) + if !entities.isEmpty { + attributes.append(TextEntitiesMessageAttribute(entities: entities)) + } + textEnqueueMessage = .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + } + if peers.count > 1 { + var enqueueMessages: [EnqueueMessage] = [] + if let textEnqueueMessage = textEnqueueMessage { + enqueueMessages.append(textEnqueueMessage) + } + for peer in peers { + var media: TelegramMediaContact? + switch peer { + case let .peer(contact, _, _): + guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + continue + } + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + let phone = contactData.basicData.phoneNumbers[0].value + media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil) + case let .deviceContact(_, basicData): + guard !basicData.phoneNumbers.isEmpty else { + continue + } + let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + let phone = contactData.basicData.phoneNumbers[0].value + media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil) + } + + if let media = media { + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + enqueueMessages.append(message) + } + } + strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) + } else if let peer = peers.first { + let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> + switch peer { + case let .peer(contact, _, _): + guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + return + } + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + let context = strongSelf.context + dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:])) + |> take(1) + |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in + var stableId: String? + let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) + outer: for (id, data) in basicData { + for phoneNumber in data.phoneNumbers { + if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber { + stableId = id + break outer + } + } + } + + if let stableId = stableId { + return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) + |> take(1) + |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + return (contact, extendedData) + } + } else { + return .single((contact, contactData)) + } + } + case let .deviceContact(id, _): + dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil)) + |> take(1) + |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + return (nil, extendedData) + } + } + strongSelf.controllerNavigationDisposable.set((dataSignal + |> deliverOnMainQueue).startStrict(next: { peerAndContactData in + if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 { + if contactData.isPrimitive { + let phone = contactData.basicData.phoneNumbers[0].value + let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil) + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + + var enqueueMessages: [EnqueueMessage] = [] + if let textEnqueueMessage = textEnqueueMessage { + enqueueMessages.append(textEnqueueMessage) + } + enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) + } else { + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { + return + } + let phone = contactData.basicData.phoneNumbers[0].value + if let vCardData = contactData.serializedVCard() { + let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData) + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + + var enqueueMessages: [EnqueueMessage] = [] + if let textEnqueueMessage = textEnqueueMessage { + enqueueMessages.append(textEnqueueMessage) + } + enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) + } + }), completed: nil, cancelled: nil) + strongSelf.effectiveNavigationController?.pushViewController(contactController) + } + } + })) + } + } + })) + case .poll: + let controller = strongSelf.configurePollCreation() + completion(controller, controller?.mediaPickerContext) + strongSelf.controllerNavigationDisposable.set(nil) + case .gift: + let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions + if !premiumGiftOptions.isEmpty { + let controller = PremiumGiftAttachmentScreen(context: context, peerIds: [peer.id], options: premiumGiftOptions, source: .attachMenu, pushController: { [weak self] c in + if let strongSelf = self { + strongSelf.push(c) + } + }, completion: { [weak self] in + if let strongSelf = self { + strongSelf.hintPlayNextOutgoingGift() + strongSelf.attachmentController?.dismiss(animated: true) + } + }) + completion(controller, controller.mediaPickerContext) + strongSelf.controllerNavigationDisposable.set(nil) + + let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peer.id).startStandalone() + } + case let .app(bot): + var payload: String? + var fromAttachMenu = true + if case let .bot(_, botPayload, _) = subject { + payload = botPayload + fromAttachMenu = false + } + let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false) + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageSubject?.messageId, threadId: strongSelf.chatLocation.threadId) + controller.openUrl = { [weak self] url, concealed, commit in + self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) + } + controller.getNavigationController = { [weak self] in + return self?.effectiveNavigationController + } + controller.completion = { [weak self] in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() + } + } + completion(controller, controller.mediaPickerContext) + strongSelf.controllerNavigationDisposable.set(nil) + + if bot.flags.contains(.notActivated) { + let alertController = webAppTermsAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bot: bot, completion: { [weak self] allowWrite in + guard let self else { + return + } + if bot.flags.contains(.showInSettingsDisclaimer) { + let _ = self.context.engine.messages.acceptAttachMenuBotDisclaimer(botId: bot.peer.id).startStandalone() + } + let _ = (self.context.engine.messages.addBotToAttachMenu(botId: bot.peer.id, allowWrite: allowWrite) + |> deliverOnMainQueue).startStandalone(error: { _ in + }, completed: { [weak controller] in + controller?.refresh() + }) + }, + dismissed: { + strongSelf.attachmentController?.dismiss(animated: true) + }) + strongSelf.present(alertController, in: .window(.root)) + } + default: + break + } + } + let present = { + attachmentController.navigationPresentation = .flatModal + strongSelf.push(attachmentController) + strongSelf.attachmentController = attachmentController + + if case let .bot(botId, _, botJustInstalled) = subject, botJustInstalled { + if let button = allButtons.first(where: { button in + if case let .app(bot) = button, bot.peer.id == botId { + return true + } else { + return false + } + }), case let .app(bot) = button { + Queue.mainQueue().after(0.3) { + let content: UndoOverlayContent + if bot.flags.contains(.showInSettings) { + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(bot.shortName).string, timeout: 5.0, customUndoText: nil) + } else { + content = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsAdded(bot.shortName).string, timeout: 5.0, customUndoText: nil) + } + attachmentController.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) + } + } + } + } + + if inputIsActive { + Queue.mainQueue().after(0.15, { + present() + }) + } else { + present() + } + }) + } + + func oldPresentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) { + let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in + let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) + return entry ?? GeneratedMediaStoreSettings.defaultSettings + } + |> deliverOnMainQueue).startStandalone(next: { [weak self] settings in + guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + return + } + strongSelf.chatDisplayNode.dismissInput() + + var bannedSendMedia: (Int32, Bool)? + var canSendPolls = true + if let channel = peer as? TelegramChannel { + if let value = channel.hasBannedPermission(.banSendMedia) { + bannedSendMedia = value + } + if channel.hasBannedPermission(.banSendPolls) != nil { + canSendPolls = false + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendMedia) { + bannedSendMedia = (Int32.max, false) + } + if group.hasBannedPermission(.banSendPolls) { + canSendPolls = false + } + } + + if editMediaOptions == nil, let (untilDate, personal) = bannedSendMedia { + let banDescription: String + if untilDate != 0 && untilDate != Int32.max { + banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedMediaTimed(stringForFullDate(timestamp: untilDate, strings: strongSelf.presentationInterfaceState.strings, dateTimeFormat: strongSelf.presentationInterfaceState.dateTimeFormat)).string + } else if personal { + banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedMedia + } else { + banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedMedia + } + + let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) + var items: [ActionSheetItem] = [] + items.append(ActionSheetTextItem(title: banDescription)) + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Location, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + self?.presentLocationPicker() + })) + if canSendPolls { + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.AttachmentMenu_Poll, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let controller = self?.configurePollCreation() { + self?.effectiveNavigationController?.pushViewController(controller) + } + })) + } + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Contact, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + self?.presentContactPicker() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.present(actionSheet, in: .window(.root)) + + return + } + + let legacyController = LegacyController(presentation: .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) + legacyController.blocksBackgroundWhenInOverlay = true + legacyController.acceptsFocusWhenInOverlay = true + legacyController.statusBar.statusBarStyle = .Ignore + legacyController.controllerLoaded = { [weak legacyController] in + legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true + } + + let emptyController = LegacyEmptyController(context: legacyController.context)! + let navigationController = makeLegacyNavigationController(rootController: emptyController) + navigationController.setNavigationBarHidden(true, animated: false) + legacyController.bind(controller: navigationController) + + legacyController.enableSizeClassSignal = true + + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText + let menuEditMediaOptions = editMediaOptions.flatMap { options -> LegacyAttachmentMenuMediaEditing in + var result: LegacyAttachmentMenuMediaEditing = .none + if options.contains(.imageOrVideo) { + result = .imageOrVideo(editMediaReference) + } + return result + } + + var slowModeEnabled = false + if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode { + slowModeEnabled = true + } + + let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText, openGallery: { + self?.presentOldMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting, scheduleTime in + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } + if editMediaOptions != nil { + self?.editMessageMediaWithLegacySignals(signals) + } else { + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) + } + }) + }, openCamera: { [weak self] cameraView, menuController in + if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + var enablePhoto = true + var enableVideo = true + + if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall { + enableVideo = false + } + + var bannedSendPhotos: (Int32, Bool)? + var bannedSendVideos: (Int32, Bool)? + + if let channel = peer as? TelegramChannel { + if let value = channel.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = value + } + if let value = channel.hasBannedPermission(.banSendVideos) { + bannedSendVideos = value + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = (Int32.max, false) + } + if group.hasBannedPermission(.banSendVideos) { + bannedSendVideos = (Int32.max, false) + } + } + + if bannedSendPhotos != nil { + enablePhoto = false + } + if bannedSendVideos != nil { + enableVideo = false + } + + presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: peer.id.namespace != Namespaces.Peer.SecretChat, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + if let strongSelf = self { + if editMediaOptions != nil { + strongSelf.editMessageMediaWithLegacySignals(signals!) + } else { + strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) + } + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } + } + }, recognizedQRCode: { [weak self] code in + if let strongSelf = self { + if let (host, port, username, password, secret) = parseProxyUrl(code) { + strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil) + } + } + }, presentSchedulePicker: { [weak self] _, done in + if let strongSelf = self { + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + strongSelf.openScheduledMessages() + } + } + }) + } + }, presentTimerPicker: { [weak self] done in + if let strongSelf = self { + strongSelf.presentTimerPicker(style: .media, completion: { time in + done(time) + }) + } + }, getCaptionPanelView: { [weak self] in + return self?.getCaptionPanelView() + }) + } + }, openFileGallery: { + self?.presentFileMediaPickerOptions(editingMessage: editMediaOptions != nil) + }, openWebSearch: { [weak self] in + self?.presentWebSearch(editingMessage: editMediaOptions != nil, attachment: false, present: { [weak self] c, a in + self?.present(c, in: .window(.root), with: a) + }) + }, openMap: { + self?.presentLocationPicker() + }, openContacts: { + self?.presentContactPicker() + }, openPoll: { + if let controller = self?.configurePollCreation() { + self?.effectiveNavigationController?.pushViewController(controller) + } + }, presentSelectionLimitExceeded: { + guard let strongSelf = self else { + return + } + let text: String + if slowModeEnabled { + text = strongSelf.presentationData.strings.Chat_SlowmodeAttachmentLimitReached + } else { + text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + }, presentCantSendMultipleFiles: { + guard let strongSelf = self else { + return + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_AttachmentMultipleFilesDisabled, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + }, presentJpegConversionAlert: { completion in + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.MediaPicker_KeepHeic, action: { + completion(false) + }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.MediaPicker_ConvertToJpeg, action: { + completion(true) + })], actionLayout: .vertical), in: .window(.root)) + }, presentSchedulePicker: { [weak self] _, done in + if let strongSelf = self { + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + strongSelf.openScheduledMessages() + } + } + }) + } + }, presentTimerPicker: { [weak self] done in + if let strongSelf = self { + strongSelf.presentTimerPicker(style: .media, completion: { time in + done(time) + }) + } + }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + guard let strongSelf = self else { + completion() + return + } + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } + if editMediaOptions != nil { + strongSelf.editMessageMediaWithLegacySignals(signals!) + completion() + } else { + let immediateCompletion = getAnimatedTransitionSource == nil + strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: { + if !immediateCompletion { + completion() + } + }) + if immediateCompletion { + completion() + } + } + }, selectRecentlyUsedInlineBot: { [weak self] peer in + if let strongSelf = self, let addressName = peer.addressName { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState({ $0.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: "@" + addressName + " "))) }).updatedInputMode({ _ in + return .text + }) + }) + } + }, getCaptionPanelView: { [weak self] in + return self?.getCaptionPanelView() + }, present: { [weak self] c, a in + self?.present(c, in: .window(.root), with: a) + }) + controller.didDismiss = { [weak legacyController] _ in + legacyController?.dismiss() + } + controller.customRemoveFromParentViewController = { [weak legacyController] in + legacyController?.dismiss() + } + + legacyController.blocksBackgroundWhenInOverlay = true + strongSelf.present(legacyController, in: .window(.root)) + controller.present(in: emptyController, sourceView: nil, animated: true) + + let presentationDisposable = strongSelf.updatedPresentationData.1.startStrict(next: { [weak controller] presentationData in + if let controller = controller { + controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme, forceDark: false) + } + }) + legacyController.disposables.add(presentationDisposable) + }) + } + + func presentFileGallery(editingMessage: Bool = false) { + self.presentOldMediaPicker(fileMode: true, editingMedia: editingMessage, completion: { [weak self] signals, silentPosting, scheduleTime in + if editingMessage { + self?.editMessageMediaWithLegacySignals(signals) + } else { + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) + } + }) + } + + func presentICloudFileGallery(editingMessage: Bool = false) { + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] result in + guard let strongSelf = self else { + return + } + let (accountPeer, limits, premiumLimits) = result + let isPremium = accountPeer?.isPremium ?? false + + strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, completion: { [weak self] urls in + if let strongSelf = self, !urls.isEmpty { + var signals: [Signal] = [] + for url in urls { + signals.append(iCloudFileDescription(url)) + } + strongSelf.enqueueMediaMessageDisposable.set((combineLatest(signals) + |> deliverOnMainQueue).startStrict(next: { results in + if let strongSelf = self { + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + + for item in results { + if let item = item { + if item.fileSize > Int64(premiumLimits.maxUploadFileParts) * 512 * 1024 { + let controller = PremiumLimitScreen(context: strongSelf.context, subject: .files, count: 4, action: { + return true + }) + strongSelf.push(controller) + return + } else if item.fileSize > Int64(limits.maxUploadFileParts) * 512 * 1024 && !isPremium { + let context = strongSelf.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .files, count: 2, action: { + replaceImpl?(PremiumIntroScreen(context: context, source: .upload)) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + strongSelf.push(controller) + return + } + } + } + + var groupingKey: Int64? + var fileTypes: (music: Bool, other: Bool) = (false, false) + if results.count > 1 { + for item in results { + if let item = item { + let pathExtension = (item.fileName as NSString).pathExtension.lowercased() + if ["mp3", "m4a"].contains(pathExtension) { + fileTypes.music = true + } else { + fileTypes.other = true + } + } + } + } + if fileTypes.music != fileTypes.other { + groupingKey = Int64.random(in: Int64.min ... Int64.max) + } + + var messages: [EnqueueMessage] = [] + for item in results { + if let item = item { + let fileId = Int64.random(in: Int64.min ... Int64.max) + let mimeType = guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension) + var previewRepresentations: [TelegramMediaImageRepresentation] = [] + if mimeType.hasPrefix("image/") || mimeType == "application/pdf" { + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 320, height: 320), resource: ICloudFileResource(urlData: item.urlData, thumbnail: true), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) + } + var attributes: [TelegramMediaFileAttribute] = [] + attributes.append(.FileName(fileName: item.fileName)) + if let audioMetadata = item.audioMetadata { + attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil)) + } + + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes) + let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []) + messages.append(message) + } + if let _ = groupingKey, messages.count % 10 == 0 { + groupingKey = Int64.random(in: Int64.min ... Int64.max) + } + } + + if !messages.isEmpty { + if editingMessage { + strongSelf.editMessageMediaWithMessages(messages) + } else { + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + strongSelf.sendMessages(messages) + } + } + } + })) + } + }), in: .window(.root)) + }) + } + + func presentFileMediaPickerOptions(editingMessage: Bool) { + let actionSheet = ActionSheetController(presentationData: self.presentationData) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Conversation_FilePhotoOrVideo, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.presentFileGallery(editingMessage: editingMessage) + } + }), + ActionSheetButtonItem(title: self.presentationData.strings.Conversation_FileICloudDrive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.presentICloudFileGallery(editingMessage: editingMessage) + } + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + self.chatDisplayNode.dismissInput() + self.present(actionSheet, in: .window(.root)) + } + + func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return + } + var isScheduledMessages = false + if case .scheduledMessages = self.presentationInterfaceState.subject { + isScheduledMessages = true + } + let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), threadTitle: self.threadInfo?.title, chatLocation: self.chatLocation, isScheduledMessages: isScheduledMessages, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, subject: subject, saveEditedPhotos: saveEditedPhotos) + let mediaPickerContext = controller.mediaPickerContext + controller.openCamera = { [weak self] cameraView in + self?.openCamera(cameraView: cameraView) + } + controller.presentWebSearch = { [weak self, weak controller] mediaGroups, activateOnDisplay in + self?.presentWebSearch(editingMessage: false, attachment: true, activateOnDisplay: activateOnDisplay, present: { [weak controller] c, a in + controller?.present(c, in: .current) + if let webSearchController = c as? WebSearchController { + webSearchController.searchingUpdated = { [weak mediaGroups] searching in + if let mediaGroups = mediaGroups, mediaGroups.isNodeLoaded { + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: mediaGroups.displayNode, alpha: searching ? 0.0 : 1.0) + mediaGroups.displayNode.isUserInteractionEnabled = !searching + } + } + webSearchController.present(mediaGroups, in: .current) + webSearchController.dismissed = { + updateMediaPickerContext(mediaPickerContext) + } + controller?.webSearchController = webSearchController + updateMediaPickerContext(webSearchController.mediaPickerContext) + } + }) + } + controller.presentSchedulePicker = { [weak self] media, done in + if let strongSelf = self { + strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + strongSelf.openScheduledMessages() + } + } + }) + } + } + controller.presentTimerPicker = { [weak self] done in + if let strongSelf = self { + strongSelf.presentTimerPicker(style: .media, completion: { time in + done(time) + }) + } + } + controller.getCaptionPanelView = { [weak self] in + return self?.getCaptionPanelView() + } + controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in + completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion) + } + present(controller, mediaPickerContext) + } + + func presentOldMediaPicker(fileMode: Bool, editingMedia: Bool, completion: @escaping ([Any], Bool, Int32) -> Void) { + let engine = self.context.engine + let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Signal<(GeneratedMediaStoreSettings, EngineConfiguration.SearchBots), NoError> in + let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) + + return engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) + |> map { configuration -> (GeneratedMediaStoreSettings, EngineConfiguration.SearchBots) in + return (entry ?? GeneratedMediaStoreSettings.defaultSettings, configuration) + } + } + |> switchToLatest + |> deliverOnMainQueue).startStandalone(next: { [weak self] settings, searchBotsConfiguration in + guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + return + } + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText + var selectionLimit: Int = 100 + var slowModeEnabled = false + if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode { + selectionLimit = 10 + slowModeEnabled = true + } + + let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, threadTitle: strongSelf.threadInfo?.title, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, selectionLimit: selectionLimit).startStandalone(next: { generator in + if let strongSelf = self { + let legacyController = LegacyController(presentation: fileMode ? .navigation : .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) + legacyController.navigationPresentation = .modal + legacyController.statusBar.statusBarStyle = strongSelf.presentationData.theme.rootController.statusBarStyle.style + legacyController.controllerLoaded = { [weak legacyController] in + legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true + legacyController?.view.disablesInteractiveModalDismiss = true + } + let controller = generator(legacyController.context) + + legacyController.bind(controller: controller) + legacyController.deferScreenEdgeGestures = [.top] + + configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: editingMedia ? nil : { [weak self, weak legacyController] in + if let strongSelf = self { + let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), chatLocation: strongSelf.chatLocation, configuration: searchBotsConfiguration, mode: .media(attachment: false, completion: { results, selectionState, editingState, silentPosting in + if let legacyController = legacyController { + legacyController.dismiss() + } + legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { result in + if let strongSelf = self { + strongSelf.enqueueChatContextResult(results, result, hideVia: true) + } + }, enqueueMediaMessages: { signals in + if let strongSelf = self { + if editingMedia { + strongSelf.editMessageMediaWithLegacySignals(signals) + } else { + strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) + } + } + }) + })) + controller.getCaptionPanelView = { [weak self] in + return self?.getCaptionPanelView() + } + strongSelf.effectiveNavigationController?.pushViewController(controller) + } + }, presentSelectionLimitExceeded: { + guard let strongSelf = self else { + return + } + + let text: String + if slowModeEnabled { + text = strongSelf.presentationData.strings.Chat_SlowmodeAttachmentLimitReached + } else { + text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached + } + + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + }, presentSchedulePicker: { [weak self] media, done in + if let strongSelf = self { + strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + strongSelf.openScheduledMessages() + } + } + }) + } + }, presentTimerPicker: { [weak self] done in + if let strongSelf = self { + strongSelf.presentTimerPicker(style: .media, completion: { time in + done(time) + }) + } + }, getCaptionPanelView: { [weak self] in + return self?.getCaptionPanelView() + }) + controller.descriptionGenerator = legacyAssetPickerItemGenerator() + controller.completionBlock = { [weak legacyController] signals, silentPosting, scheduleTime in + if let legacyController = legacyController { + legacyController.dismiss(animated: true) + completion(signals!, silentPosting, scheduleTime) + } + } + controller.dismissalBlock = { [weak legacyController] in + if let legacyController = legacyController { + legacyController.dismiss(animated: true) + } + } + strongSelf.chatDisplayNode.dismissInput() + strongSelf.effectiveNavigationController?.pushViewController(legacyController) + } + }) + }) + } + + func presentWebSearch(editingMessage: Bool, attachment: Bool, activateOnDisplay: Bool = true, present: @escaping (ViewController, Any?) -> Void) { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return + } + + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) + |> deliverOnMainQueue).startStandalone(next: { [weak self] configuration in + if let strongSelf = self { + let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), chatLocation: strongSelf.chatLocation, configuration: configuration, mode: .media(attachment: attachment, completion: { [weak self] results, selectionState, editingState, silentPosting in + self?.attachmentController?.dismiss(animated: true, completion: nil) + legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak self] result in + if let strongSelf = self { + strongSelf.enqueueChatContextResult(results, result, hideVia: true) + } + }, enqueueMediaMessages: { [weak self] signals in + if let strongSelf = self, !signals.isEmpty { + if editingMessage { + strongSelf.editMessageMediaWithLegacySignals(signals) + } else { + strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) + } + } + }) + }), activateOnDisplay: activateOnDisplay) + controller.attemptItemSelection = { [weak strongSelf] item in + guard let strongSelf, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + return false + } + + enum ItemType { + case gif + case image + case video + } + + var itemType: ItemType? + switch item { + case let .internalReference(reference): + if reference.type == "gif" { + itemType = .gif + } else if reference.type == "photo" { + itemType = .image + } else if reference.type == "video" { + itemType = .video + } + case let .externalReference(reference): + if reference.type == "gif" { + itemType = .gif + } else if reference.type == "photo" { + itemType = .image + } else if reference.type == "video" { + itemType = .video + } + } + + var bannedSendPhotos: (Int32, Bool)? + var bannedSendVideos: (Int32, Bool)? + var bannedSendGifs: (Int32, Bool)? + + if let channel = peer as? TelegramChannel { + if let value = channel.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = value + } + if let value = channel.hasBannedPermission(.banSendVideos) { + bannedSendVideos = value + } + if let value = channel.hasBannedPermission(.banSendGifs) { + bannedSendGifs = value + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = (Int32.max, false) + } + if group.hasBannedPermission(.banSendVideos) { + bannedSendVideos = (Int32.max, false) + } + if group.hasBannedPermission(.banSendGifs) { + bannedSendGifs = (Int32.max, false) + } + } + + if let itemType { + switch itemType { + case .image: + if bannedSendPhotos != nil { + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + + return false + } + case .video: + if bannedSendVideos != nil { + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + + return false + } + case .gif: + if bannedSendGifs != nil { + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + + return false + } + } + } + + return true + } + controller.getCaptionPanelView = { [weak strongSelf] in + return strongSelf?.getCaptionPanelView() + } + present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } + }) + } + + func presentLocationPicker() { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return + } + let selfPeerId: PeerId + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + selfPeerId = peer.id + } else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) { + selfPeerId = peer.id + } else { + selfPeerId = self.context.account.peerId + } + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: selfPeerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] selfPeer in + guard let strongSelf = self, let selfPeer = selfPeer else { + return + } + let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages + let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _, _ in + guard let strongSelf = self else { + return + } + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + strongSelf.sendMessages([message]) + }) + strongSelf.effectiveNavigationController?.pushViewController(controller) + strongSelf.chatDisplayNode.dismissInput() + }) + } + + func presentContactPicker() { + let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true)) + contactsController.navigationPresentation = .modal + self.chatDisplayNode.dismissInput() + self.effectiveNavigationController?.pushViewController(contactsController) + self.controllerNavigationDisposable.set((contactsController.result + |> deliverOnMainQueue).startStrict(next: { [weak self] peers in + if let strongSelf = self, let (peers, _, _, _, _) = peers { + if peers.count > 1 { + var enqueueMessages: [EnqueueMessage] = [] + for peer in peers { + var media: TelegramMediaContact? + switch peer { + case let .peer(contact, _, _): + guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + continue + } + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + let phone = contactData.basicData.phoneNumbers[0].value + media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil) + case let .deviceContact(_, basicData): + guard !basicData.phoneNumbers.isEmpty else { + continue + } + let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + let phone = contactData.basicData.phoneNumbers[0].value + media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil) + } + + if let media = media { + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + enqueueMessages.append(message) + } + } + strongSelf.sendMessages(enqueueMessages) + } else if let peer = peers.first { + let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> + switch peer { + case let .peer(contact, _, _): + guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + return + } + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + let context = strongSelf.context + dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:])) + |> take(1) + |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in + var stableId: String? + let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) + outer: for (id, data) in basicData { + for phoneNumber in data.phoneNumbers { + if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber { + stableId = id + break outer + } + } + } + + if let stableId = stableId { + return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) + |> take(1) + |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + return (contact, extendedData) + } + } else { + return .single((contact, contactData)) + } + } + case let .deviceContact(id, _): + dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil)) + |> take(1) + |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + return (nil, extendedData) + } + } + strongSelf.controllerNavigationDisposable.set((dataSignal + |> deliverOnMainQueue).startStrict(next: { peerAndContactData in + if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 { + if contactData.isPrimitive { + let phone = contactData.basicData.phoneNumbers[0].value + let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil) + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + strongSelf.sendMessages([message]) + } else { + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { + return + } + let phone = contactData.basicData.phoneNumbers[0].value + if let vCardData = contactData.serializedVCard() { + let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData) + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, nil) + let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + strongSelf.sendMessages([message]) + } + }), completed: nil, cancelled: nil) + strongSelf.effectiveNavigationController?.pushViewController(contactController) + } + } + })) + } + } + })) + } + + func getCaptionPanelView() -> TGCaptionPanelView? { + var isScheduledMessages = false + if case .scheduledMessages = self.presentationInterfaceState.subject { + isScheduledMessages = true + } + return self.context.sharedContext.makeGalleryCaptionPanelView(context: self.context, chatLocation: self.presentationInterfaceState.chatLocation, isScheduledMessages: isScheduledMessages, customEmojiAvailable: self.presentationInterfaceState.customEmojiAvailable, present: { [weak self] c in + self?.present(c, in: .window(.root)) + }, presentInGlobalOverlay: { [weak self] c in + guard let self else { + return + } + self.presentInGlobalOverlay(c) + }) as? TGCaptionPanelView + } + + func openCamera(cameraView: TGAttachmentCameraView? = nil) { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return + } + let _ = peer + + let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in + let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) + return entry ?? GeneratedMediaStoreSettings.defaultSettings + } + |> deliverOnMainQueue).startStandalone(next: { [weak self] settings in + guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + return + } + + var enablePhoto = true + var enableVideo = true + + if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall { + enableVideo = false + } + + var bannedSendPhotos: (Int32, Bool)? + var bannedSendVideos: (Int32, Bool)? + + if let channel = peer as? TelegramChannel { + if let value = channel.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = value + } + if let value = channel.hasBannedPermission(.banSendVideos) { + bannedSendVideos = value + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = (Int32.max, false) + } + if group.hasBannedPermission(.banSendVideos) { + bannedSendVideos = (Int32.max, false) + } + } + + if bannedSendPhotos != nil { + enablePhoto = false + } + if bannedSendVideos != nil { + enableVideo = false + } + + let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText + + presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + if let strongSelf = self { + strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) + if !inputText.string.isEmpty { + strongSelf.clearInputText() + } + } + }, recognizedQRCode: { [weak self] code in + if let strongSelf = self { + if let (host, port, username, password, secret) = parseProxyUrl(code) { + strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil) + } + } + }, presentSchedulePicker: { [weak self] _, done in + if let strongSelf = self { + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in + if let strongSelf = self { + done(time) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + strongSelf.openScheduledMessages() + } + } + }) + } + }, presentTimerPicker: { [weak self] done in + if let strongSelf = self { + strongSelf.presentTimerPicker(style: .media, completion: { time in + done(time) + }) + } + }, getCaptionPanelView: { [weak self] in + return self?.getCaptionPanelView() + }, dismissedWithResult: { [weak self] in + self?.attachmentController?.dismiss(animated: false, completion: nil) + }, finishedTransitionIn: { [weak self] in + self?.attachmentController?.scrollToTop?() + }) + }) + } +} diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index fb6d8dc00b..5983a2c37a 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -1122,25 +1122,25 @@ final class ChatEmptyNode: ASDisplayNode { } func animateFromLoadingNode(_ loadingNode: ChatLoadingNode) { - guard let (_, node) = content else { + guard let (_, node) = self.content else { return } - let duration: Double = 0.2 - node.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) - node.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) + let duration: Double = 0.3 + node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + node.layer.animateScale(from: 0.01, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) let targetCornerRadius = self.backgroundNode.backgroundCornerRadius let targetFrame = self.backgroundNode.frame let initialFrame = loadingNode.convert(loadingNode.progressFrame, to: self) - let transition = ContainedViewLayoutTransition.animated(duration: duration, curve: .easeInOut) - self.backgroundNode.layer.animateFrame(from: initialFrame, to: targetFrame, duration: duration) + let transition = ContainedViewLayoutTransition.animated(duration: duration, curve: .spring) + self.backgroundNode.layer.animateFrame(from: initialFrame, to: targetFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) self.backgroundNode.update(size: initialFrame.size, cornerRadius: initialFrame.size.width / 2.0, transition: .immediate) self.backgroundNode.update(size: targetFrame.size, cornerRadius: targetCornerRadius, transition: transition) if let backgroundContent = self.backgroundContent { - backgroundContent.layer.animateFrame(from: initialFrame, to: targetFrame, duration: duration) + backgroundContent.layer.animateFrame(from: initialFrame, to: targetFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) backgroundContent.cornerRadius = initialFrame.size.width / 2.0 transition.updateCornerRadius(layer: backgroundContent.layer, cornerRadius: targetCornerRadius) } diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index d0b8be28c3..3af4fe35df 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -187,11 +187,11 @@ func chatHistoryEntriesForView( } if presentationData.largeEmoji, message.media.isEmpty { - if messageIsElligibleForLargeCustomEmoji(message) { + if messageIsEligibleForLargeCustomEmoji(message) { contentTypeHint = .animatedEmoji } else if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0], (message.textEntitiesAttribute?.entities.isEmpty ?? true) { contentTypeHint = .animatedEmoji - } else if messageIsElligibleForLargeEmoji(message) { + } else if messageIsEligibleForLargeEmoji(message) { contentTypeHint = .animatedEmoji } } @@ -323,11 +323,11 @@ func chatHistoryEntriesForView( var contentTypeHint: ChatMessageEntryContentType = .generic if presentationData.largeEmoji, topMessage.media.isEmpty { - if messageIsElligibleForLargeCustomEmoji(topMessage) { + if messageIsEligibleForLargeCustomEmoji(topMessage) { contentTypeHint = .animatedEmoji } else if stickersEnabled && topMessage.text.count == 1, let _ = associatedData.animatedEmojiStickers[topMessage.text.basicEmoji.0] { contentTypeHint = .animatedEmoji - } else if messageIsElligibleForLargeEmoji(topMessage) { + } else if messageIsEligibleForLargeEmoji(topMessage) { contentTypeHint = .animatedEmoji } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 77bc4dbf5d..b34dbbaaba 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -376,7 +376,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState return (panel, nil) } } else { - if let _ = chatPresentationInterfaceState.recordedMediaPreview { + if let _ = chatPresentationInterfaceState.interfaceState.mediaDraftState { if let currentPanel = (currentPanel as? ChatRecordingPreviewInputPanelNode) ?? (currentSecondaryPanel as? ChatRecordingPreviewInputPanelNode) { return (currentPanel, nil) } else { diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index b29d44420a..3789f17201 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -245,7 +245,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { } if self.presentationInterfaceState != interfaceState { var updateWaveform = false - if self.presentationInterfaceState?.recordedMediaPreview != interfaceState.recordedMediaPreview { + if self.presentationInterfaceState?.interfaceState.mediaDraftState != interfaceState.interfaceState.mediaDraftState { updateWaveform = true } if self.presentationInterfaceState?.strings !== interfaceState.strings { @@ -256,7 +256,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { self.presentationInterfaceState = interfaceState - if let recordedMediaPreview = interfaceState.recordedMediaPreview, let context = self.context { + if let recordedMediaPreview = interfaceState.interfaceState.mediaDraftState, let context = self.context { switch recordedMediaPreview { case let .audio(audio): self.waveformButton.isHidden = false @@ -329,7 +329,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { ], positionUpdated: { _, _ in }, trackTrimUpdated: { _, start, end, updatedEnd, apply in - video.control.updateTrimRange(start, end, updatedEnd, apply) +// video.control.updateTrimRange(start, end, updatedEnd, apply) }, trackOffsetUpdated: { _, _, _ in }, trackLongPressed: { _, _ in } diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index 75efeff8ef..2325486c84 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -11,6 +11,7 @@ import TelegramPresentationData import ChatInputPanelNode final class ChatRestrictedInputPanelNode: ChatInputPanelNode { + private let buttonNode: HighlightTrackingButtonNode private let textNode: ImmediateTextNode private var iconView: UIImageView? @@ -21,9 +22,34 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { self.textNode.maximumNumberOfLines = 2 self.textNode.textAlignment = .center + self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode.isUserInteractionEnabled = false + super.init() self.addSubnode(self.textNode) + self.addSubnode(self.buttonNode) + + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let self { + if highlighted { + self.iconView?.layer.removeAnimation(forKey: "opacity") + self.iconView?.alpha = 0.4 + self.textNode.layer.removeAnimation(forKey: "opacity") + self.textNode.alpha = 0.4 + } else { + self.iconView?.alpha = 1.0 + self.iconView?.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + self.textNode.alpha = 1.0 + self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + @objc private func buttonPressed() { + self.interfaceInteraction?.openBoostToUnrestrict() } override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { @@ -45,6 +71,8 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { } var iconImage: UIImage? + var iconSpacing: CGFloat = 4.0 + var isUserInteractionEnabled = false if case let .replyThread(message) = interfaceState.chatLocation, message.peerId == self.context?.account.peerId { self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelStatusAuthorHidden, font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor) @@ -63,17 +91,25 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { } else if personal { self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_RestrictedText, font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor) } else { - self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_DefaultRestrictedText, font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor) + if "".isEmpty { + //TODO:localize + iconSpacing = 0.0 + iconImage = PresentationResourcesChat.chatPanelBoostIcon(interfaceState.theme) + self.textNode.attributedText = NSAttributedString(string: "Boost this group to send messages", font: Font.regular(15.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor) + isUserInteractionEnabled = true + } else { + self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_DefaultRestrictedText, font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor) + } } } + self.buttonNode.isUserInteractionEnabled = isUserInteractionEnabled let panelHeight = defaultHeight(metrics: metrics) let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight)) - let textFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - textSize.width) / 2.0), y: floor((panelHeight - textSize.height) / 2.0)), size: textSize) - self.textNode.frame = textFrame + var originX: CGFloat = leftInset + floor((width - leftInset - rightInset - textSize.width) / 2.0) - if let iconImage = iconImage { + if let iconImage { let iconView: UIImageView if let current = self.iconView { iconView = current @@ -83,12 +119,21 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { self.view.addSubview(iconView) } iconView.image = iconImage - iconView.frame = CGRect(origin: CGPoint(x: textFrame.minX - 4.0 - iconImage.size.width, y: textFrame.minY + UIScreenPixel + floorToScreenPixels((textFrame.height - iconImage.size.height) / 2.0)), size: iconImage.size) + + let totalWidth = textSize.width + iconImage.size.width + iconSpacing + iconView.frame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - totalWidth) / 2.0), y: floor((panelHeight - textSize.height) / 2.0) + UIScreenPixel + floorToScreenPixels((textSize.height - iconImage.size.height) / 2.0)), size: iconImage.size) + + originX += iconImage.size.width + iconSpacing } else if let iconView = self.iconView { self.iconView = nil iconView.removeFromSuperview() } + let textFrame = CGRect(origin: CGPoint(x: originX, y: floor((panelHeight - textSize.height) / 2.0)), size: textSize) + self.textNode.frame = textFrame + + self.buttonNode.frame = textFrame.insetBy(dx: -8.0, dy: -12.0) + return panelHeight } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index b2f4d0f1bb..0eef5984a6 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -100,6 +100,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, + mediaDraftContentType: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index e748ee429d..eb3d77fd2f 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -164,38 +164,38 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode { self.backdropNode.update(rect: rect, within: containerSize) } - var isScheduledMessages = false - if case .scheduledMessages = interfaceState.subject { - isScheduledMessages = true - } - - if let slowmodeState = interfaceState.slowmodeState, !isScheduledMessages && interfaceState.editMessageState == nil { - let sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode - if let current = self.sendButtonRadialStatusNode { - sendButtonRadialStatusNode = current - } else { - sendButtonRadialStatusNode = ChatSendButtonRadialStatusNode(color: interfaceState.theme.chat.inputPanel.panelControlAccentColor) - sendButtonRadialStatusNode.alpha = self.sendContainerNode.alpha - self.sendButtonRadialStatusNode = sendButtonRadialStatusNode - self.addSubnode(sendButtonRadialStatusNode) - } - - transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 0.7575, y: 0.7575)) - - let defaultSendButtonSize: CGFloat = 25.0 - let defaultOriginX = floorToScreenPixels((self.sendButton.bounds.width - defaultSendButtonSize) / 2.0) - let defaultOriginY = floorToScreenPixels((self.sendButton.bounds.height - defaultSendButtonSize) / 2.0) - - let radialStatusFrame = CGRect(origin: CGPoint(x: defaultOriginX - 4.0, y: defaultOriginY - 4.0), size: CGSize(width: 33.0, height: 33.0)) - sendButtonRadialStatusNode.frame = radialStatusFrame - sendButtonRadialStatusNode.slowmodeState = slowmodeState - } else { - if let sendButtonRadialStatusNode = self.sendButtonRadialStatusNode { - self.sendButtonRadialStatusNode = nil - sendButtonRadialStatusNode.removeFromSupernode() - } - transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 1.0, y: 1.0)) - } +// var isScheduledMessages = false +// if case .scheduledMessages = interfaceState.subject { +// isScheduledMessages = true +// } +// +// if let slowmodeState = interfaceState.slowmodeState, !isScheduledMessages && interfaceState.editMessageState == nil { +// let sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode +// if let current = self.sendButtonRadialStatusNode { +// sendButtonRadialStatusNode = current +// } else { +// sendButtonRadialStatusNode = ChatSendButtonRadialStatusNode(color: interfaceState.theme.chat.inputPanel.panelControlAccentColor) +// sendButtonRadialStatusNode.alpha = self.sendContainerNode.alpha +// self.sendButtonRadialStatusNode = sendButtonRadialStatusNode +// self.addSubnode(sendButtonRadialStatusNode) +// } +// +// transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 0.7575, y: 0.7575)) +// +// let defaultSendButtonSize: CGFloat = 25.0 +// let defaultOriginX = floorToScreenPixels((self.sendButton.bounds.width - defaultSendButtonSize) / 2.0) +// let defaultOriginY = floorToScreenPixels((self.sendButton.bounds.height - defaultSendButtonSize) / 2.0) +// +// let radialStatusFrame = CGRect(origin: CGPoint(x: defaultOriginX - 4.0, y: defaultOriginY - 4.0), size: CGSize(width: 33.0, height: 33.0)) +// sendButtonRadialStatusNode.frame = radialStatusFrame +// sendButtonRadialStatusNode.slowmodeState = slowmodeState +// } else { +// if let sendButtonRadialStatusNode = self.sendButtonRadialStatusNode { +// self.sendButtonRadialStatusNode = nil +// sendButtonRadialStatusNode.removeFromSupernode() +// } +// transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 1.0, y: 1.0)) +// } transition.updateFrame(node: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size)) let expanded = isMediaInputExpanded diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index efa1dc4316..09bf35e28a 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -41,6 +41,8 @@ import ChatContextQuery import ChatInputTextNode import ChatInputPanelNode import TelegramNotices +import AnimatedCountLabelNode +import TelegramStringFormatting private let accessoryButtonFont = Font.medium(14.0) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -535,6 +537,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch let textInputBackgroundNode: ASImageNode private var transparentTextInputBackgroundImage: UIImage? let actionButtons: ChatTextInputActionButtonsNode + private let slowModeButton: BoostSlowModeButton var mediaRecordingAccessibilityArea: AccessibilityAreaNode? private let counterTextNode: ImmediateTextNode @@ -570,6 +573,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool, Bool)? private var leftMenuInset: CGFloat = 0.0 + private var rightSlowModeInset: CGFloat = 0.0 var displayAttachmentMenu: () -> Void = { } var sendMessage: () -> Void = { } @@ -855,10 +859,18 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.counterTextNode = ImmediateTextNode() self.counterTextNode.textAlignment = .center + self.slowModeButton = BoostSlowModeButton() + self.slowModeButton.alpha = 0.0 + self.viewOnceButton = ChatRecordingViewOnceButtonNode(icon: .viewOnce) super.init() + self.slowModeButton.requestUpdate = { [weak self] in + self?.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut)) + } + self.slowModeButton.addTarget(self, action: #selector(self.slowModeButtonPressed), forControlEvents: .touchUpInside) + self.viewForOverlayContent = ChatTextViewForOverlayContent( ignoreHit: { [weak self] view, point in guard let strongSelf = self else { @@ -1045,6 +1057,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.clippingNode.addSubnode(self.actionButtons) self.clippingNode.addSubnode(self.counterTextNode) + self.clippingNode.addSubnode(self.slowModeButton) + self.clippingNode.view.addSubview(self.searchLayoutClearButton) self.textInputBackgroundNode.clipsToBounds = true @@ -1416,11 +1430,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } } - func requestLayout() { + func requestLayout(transition: ContainedViewLayoutTransition = .immediate) { guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else { return } - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { @@ -1933,11 +1947,21 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } self.leftMenuInset = leftMenuInset + var rightSlowModeInset: CGFloat = 0.0 + var slowModeButtonSize: CGSize = .zero + if let presentationInterfaceState = self.presentationInterfaceState { + slowModeButtonSize = self.slowModeButton.update(size: CGSize(width: width, height: 44.0), interfaceState: presentationInterfaceState) + if inputHasText { + rightSlowModeInset = slowModeButtonSize.width - 33.0 + } + } + self.rightSlowModeInset = rightSlowModeInset + if buttonTitleUpdated && !transition.isAnimated { transition = .animated(duration: 0.3, curve: .easeInOut) } - let baseWidth = width - leftInset - leftMenuInset - rightInset + let baseWidth = width - leftInset - leftMenuInset - rightInset - rightSlowModeInset let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: baseWidth, maxHeight: maxHeight, metrics: metrics) var panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) if displayBotStartButton { @@ -1968,7 +1992,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch transition.updateFrame(node: self.sendAsAvatarReferenceNode, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size)) transition.updateFrame(node: self.sendAsAvatarNode, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size)) - let showMenuButton = hasMenuButton && interfaceState.recordedMediaPreview == nil + let showMenuButton = hasMenuButton && interfaceState.interfaceState.mediaDraftState == nil if isSendAsButton { if interfaceState.showSendAsPeers { transition.updateTransformScale(node: self.menuButton, scale: 1.0) @@ -1997,8 +2021,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch var hideMicButton = false var audioRecordingItemsAlpha: CGFloat = 1 - if mediaRecordingState != nil || (interfaceState.recordedMediaPreview != nil && self.finishedTransitionToPreview != true) { - if interfaceState.recordedMediaPreview != nil { + if mediaRecordingState != nil || (interfaceState.interfaceState.mediaDraftState != nil && self.finishedTransitionToPreview != true) { + if interfaceState.interfaceState.mediaDraftState != nil { self.finishedTransitionToPreview = false } @@ -2043,7 +2067,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.clippingNode.insertSubnode(audioRecordingCancelIndicator, at: 0) } - let isLocked = mediaRecordingState?.isLocked ?? (interfaceState.recordedMediaPreview != nil) + let isLocked = mediaRecordingState?.isLocked ?? (interfaceState.interfaceState.mediaDraftState != nil) var hideInfo = false if let mediaRecordingState = mediaRecordingState { @@ -2332,6 +2356,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), isMediaInputExpanded: isMediaInputExpanded, transition: transition, interfaceState: presentationInterfaceState) } + let slowModeButtonFrame = CGRect(origin: CGPoint(x: hideOffset.x + width - rightInset - 5.0 - slowModeButtonSize.width + composeButtonsOffset, y: hideOffset.y + panelHeight - minimalHeight + 6.0), size: slowModeButtonSize) + transition.updateFrame(node: self.slowModeButton, frame: slowModeButtonFrame) + if let _ = interfaceState.inputTextPanelState.mediaRecordingState { let text: String = interfaceState.strings.VoiceOver_MessageContextSend let mediaRecordingAccessibilityArea: AccessibilityAreaNode @@ -2463,7 +2490,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.slowmodePlaceholderNode?.isHidden = true } - var nextButtonTopRight = CGPoint(x: hideOffset.x + width - rightInset - textFieldInsets.right - accessoryButtonInset, y: hideOffset.y + panelHeight - textFieldInsets.bottom - minimalInputHeight) + var nextButtonTopRight = CGPoint(x: hideOffset.x + width - rightInset - textFieldInsets.right - accessoryButtonInset - rightSlowModeInset, y: hideOffset.y + panelHeight - textFieldInsets.bottom - minimalInputHeight) for (item, button) in self.accessoryItemButtons.reversed() { let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight) button.updateLayout(item: item, size: buttonSize) @@ -2736,6 +2763,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch return panelHeight } + @objc private func slowModeButtonPressed() { + self.interfaceInteraction?.openBoostToUnrestrict() + } + @objc private func viewOncePressed() { guard let context = self.context, let interfaceState = self.presentationInterfaceState else { return @@ -3239,7 +3270,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch composeButtonsOffset = 44.0 } - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - self.leftMenuInset, maxHeight: maxHeight, metrics: metrics) + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - self.leftMenuInset - self.rightSlowModeInset, maxHeight: maxHeight, metrics: metrics) let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) var textFieldMinHeight: CGFloat = 33.0 if let presentationInterfaceState = self.presentationInterfaceState { @@ -3544,7 +3575,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } } - self.updateActionButtons(hasText: inputHasText, hideMicButton: hideMicButton, animated: animated) + let _ = hideMicButton +// self.updateActionButtons(hasText: inputHasText, hideMicButton: hideMicButton, animated: animated) self.updateTextHeight(animated: animated) } @@ -3605,7 +3637,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch if (hasText || self.keepSendButtonEnabled && !mediaInputIsActive) { hideMicButton = true - if self.actionButtons.sendContainerNode.alpha.isZero { + + if self.actionButtons.sendContainerNode.alpha.isZero && self.rightSlowModeInset.isZero { self.actionButtons.sendContainerNode.alpha = 1.0 self.actionButtons.sendButtonRadialStatusNode?.alpha = 1.0 self.actionButtons.updateAccessibility() @@ -3621,6 +3654,17 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } } } + if self.slowModeButton.alpha.isZero && self.rightSlowModeInset > 0.0 { + self.slowModeButton.alpha = 1.0 + if animated { + self.slowModeButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + if animateWithBounce { + self.slowModeButton.layer.animateSpring(from: NSNumber(value: Float(0.1)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.6) + } else { + self.slowModeButton.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25) + } + } + } } else { if !self.actionButtons.sendContainerNode.alpha.isZero { self.actionButtons.sendContainerNode.alpha = 0.0 @@ -3637,6 +3681,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.actionButtons.sendButtonRadialStatusNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) } } + if !self.slowModeButton.alpha.isZero { + self.slowModeButton.alpha = 0.0 + if animated { + self.slowModeButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } } } @@ -3694,7 +3744,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch private func updateTextHeight(animated: Bool) { if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, metrics, _, _) = self.validLayout { - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset, maxHeight: maxHeight, metrics: metrics) + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset - self.rightSlowModeInset, maxHeight: maxHeight, metrics: metrics) let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) if !self.bounds.size.height.isEqual(to: panelHeight) { self.updateHeight(animated) @@ -4620,3 +4670,97 @@ private final class MenuIconNode: ManagedAnimationNode { } } } + +private func generateClearImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 17.0, height: 17.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) + context.setStrokeColor(UIColor.clear.cgColor) + context.setLineCap(.round) + context.setLineWidth(1.66) + context.move(to: CGPoint(x: 6.0, y: 6.0)) + context.addLine(to: CGPoint(x: 11.0, y: 11.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 6.0, y: 6.0)) + context.addLine(to: CGPoint(x: size.width - 11.0, y: 11.0)) + context.strokePath() + }) +} + + +private final class BoostSlowModeButton: HighlightTrackingButtonNode { + let backgroundNode: ASImageNode + let textNode: ImmediateAnimatedCountLabelNode + let iconNode: ASImageNode + + private var updateTimer: SwiftSignalKit.Timer? + + var requestUpdate: () -> Void = {} + + override init(pointerStyle: PointerStyle? = nil) { + self.backgroundNode = ASImageNode() + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.clipsToBounds = true + self.backgroundNode.image = generateGradientImage(size: CGSize(width: 100.0, height: 2.0), scale: 1.0, colors: [UIColor(rgb: 0x9076ff), UIColor(rgb: 0xbc6de8)], locations: [0.0, 1.0], direction: .horizontal) + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.image = generateClearImage(color: .white) + + self.textNode = ImmediateAnimatedCountLabelNode() + self.textNode.isUserInteractionEnabled = false + + super.init(pointerStyle: pointerStyle) + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.textNode) + } + + func update(size: CGSize, interfaceState: ChatPresentationInterfaceState) -> CGSize { + var text = "" + if let slowmodeState = interfaceState.slowmodeState, case let .timestamp(validUntilTimestamp) = slowmodeState.variant { + let timestamp = CGFloat(Date().timeIntervalSince1970) + let relativeTimestamp = CGFloat(validUntilTimestamp) - timestamp + text = stringForDuration(Int32(relativeTimestamp)) + + self.updateTimer?.invalidate() + self.updateTimer = SwiftSignalKit.Timer(timeout: 1.0 / 60.0, repeat: false, completion: { [weak self] in + self?.requestUpdate() + }, queue: .mainQueue()) + self.updateTimer?.start() + } else { + self.updateTimer?.invalidate() + self.updateTimer = nil + } + + let font = Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]) + let textColor = UIColor.white + + var segments: [AnimatedCountLabelNode.Segment] = [] + var textCount = 0 + + for char in text { + if let intValue = Int(String(char)) { + segments.append(.number(intValue, NSAttributedString(string: String(char), font: font, textColor: textColor))) + } else { + segments.append(.text(textCount, NSAttributedString(string: String(char), font: font, textColor: textColor))) + textCount += 1 + } + } + self.textNode.segments = segments + + let textSize = self.textNode.updateLayout(size: CGSize(width: 200.0, height: 100.0), animated: true) + let totalSize = CGSize(width: textSize.width > 0.0 ? textSize.width + 38.0 : 33.0, height: 33.0) + + self.backgroundNode.frame = CGRect(origin: .zero, size: totalSize) + self.backgroundNode.cornerRadius = totalSize.height / 2.0 + self.textNode.frame = CGRect(origin: CGPoint(x: 9.0, y: floorToScreenPixels((totalSize.height - textSize.height) / 2.0)), size: textSize) + if let icon = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: totalSize.width - icon.size.width - 7.0, y: floorToScreenPixels((totalSize.height - icon.size.height) / 2.0)), size: icon.size) + } + return totalSize + } +} diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index 125c4ae62d..8d556fb888 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -8,6 +8,7 @@ import UniversalMediaPlayer import AccountContext import OpusBinding import ChatPresentationInterfaceState +import AudioWaveform private let kOutputBus: UInt32 = 0 private let kInputBus: UInt32 = 1 diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 364f5b6103..2c35372a74 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -866,6 +866,9 @@ func openResolvedUrlImpl( } }) case let .boost(peerId, status, myBoostStatus): + guard let status else { + return + } var isCurrent = false if case let .chat(chatPeerId, _, _) = urlContext, chatPeerId == peerId { isCurrent = true @@ -883,32 +886,25 @@ func openResolvedUrlImpl( } } - PremiumBoostScreen( + let controller = PremiumBoostLevelsScreen( context: context, - contentContext: contentContext, peerId: peerId, - isCurrent: isCurrent, + mode: .user(mode: isCurrent ? .current : .external), status: status, myBoostStatus: myBoostStatus, - forceDark: forceDark, - openPeer: { peer in + openPeer: { peer in openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) }, - presentController: { [weak navigationController] c in - (navigationController?.viewControllers.last as? ViewController)?.present(c, in: .window(.root)) - }, - pushController: { [weak navigationController] c in - navigationController?.pushViewController(c) - - if c is PremiumLimitScreen { - if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext { - storyProgressPauseContext.update(c) - } - } - }, dismissed: { - dismissedImpl?() - } + forceDark: forceDark ) + controller.disposed = { + dismissedImpl?() + } + navigationController?.pushViewController(controller) + + if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext { + storyProgressPauseContext.update(controller) + } case let .premiumGiftCode(slug): var forceDark = false if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index bedf6a8cf4..ba27451ada 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -172,6 +172,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, displayGiveawayParticipationStatus: { _ in }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in + }, openGroupBoostInfo: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index de78b8e235..8e473e0375 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1749,6 +1749,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, displayGiveawayParticipationStatus: { _ in }, openPremiumStatusInfo: { _, _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in + }, openGroupBoostInfo: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { @@ -1999,6 +2000,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .wallpapers case .messageTags: mappedSubject = .messageTags + case .lastSeen: + mappedSubject = .lastSeen + case .messagePrivacy: + mappedSubject = .messagePrivacy } return PremiumDemoScreen(context: context, subject: mappedSubject, action: action) } diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index b65d15b18f..f212669410 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -731,12 +731,6 @@ public final class TextSelectionNode: ASDisplayNode { })) } } -// if isSpeakSelectionEnabled() { -// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in -// self?.performAction(attributedText, .speak) -// self?.dismissSelection() -// })) -// } let realFullRange = NSRange(location: 0, length: attributedString.length) if range != realFullRange { diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 564c632f93..6b73742cdc 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 564c632f9368409870631d3cef75a7fc4070d45b +Subproject commit 6b73742cdc140c46a1ab1b8e3390354a9738e429 diff --git a/submodules/TooltipUI/BUILD b/submodules/TooltipUI/BUILD index 6ef6c4ed14..551a211a51 100644 --- a/submodules/TooltipUI/BUILD +++ b/submodules/TooltipUI/BUILD @@ -25,6 +25,7 @@ swift_library( "//submodules/Markdown", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/Components/BalancedTextComponent", + "//submodules/Components/MultilineTextWithEntitiesComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index f8d4f5234d..7d0975080a 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -17,6 +17,7 @@ import AvatarStoryIndicatorComponent import AccountContext import Markdown import BalancedTextComponent +import MultilineTextWithEntitiesComponent public enum TooltipActiveTextItem { case url(String, Bool) @@ -108,6 +109,8 @@ private class DownArrowsIconNode: ASDisplayNode { } private final class TooltipScreenNode: ViewControllerTracingNode { + private let context: AccountContext? + private let text: TooltipScreen.Text private let textAlignment: TooltipScreen.Alignment private let balancedTextLayout: Bool @@ -171,6 +174,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { cornerRadius: CGFloat? = nil, shouldDismissOnTouch: @escaping (CGPoint, CGRect) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) { + self.context = context self.tooltipStyle = style self.arrowStyle = arrowStyle self.icon = icon @@ -534,6 +538,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } ) attributedText = parseMarkdownIntoAttributedString(text, attributes: markdownAttributes) + case let .attributedString(text): + attributedText = text } let highlightColor: UIColor? = UIColor.white.withAlphaComponent(0.5) @@ -594,21 +600,44 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } } - let textSize = self.textView.update( - transition: .immediate, - component: AnyComponent(BalancedTextComponent( - text: .plain(attributedText), - balanced: self.balancedTextLayout, - horizontalAlignment: self.textAlignment == .center ? .center : .left, - maximumNumberOfLines: 0, - highlightColor: highlightColor, - highlightAction: highlightAction, - tapAction: tapAction, - longTapAction: longTapAction - )), - environment: {}, - containerSize: CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: 1000000.0) - ) + let textSize: CGSize + if case .attributedString = self.text, let context = self.context { + textSize = self.textView.update( + transition: .immediate, + component: AnyComponent(MultilineTextWithEntitiesComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.4), + text: .plain(attributedText), + horizontalAlignment: self.textAlignment == .center ? .center : .left, + truncationType: .end, + maximumNumberOfLines: 3, + lineSpacing: 0.2, + highlightAction: nil, + tapAction: { _, _ in + } + )), + environment: {}, + containerSize: CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: 1000000.0) + ) + } else { + textSize = self.textView.update( + transition: .immediate, + component: AnyComponent(BalancedTextComponent( + text: .plain(attributedText), + balanced: self.balancedTextLayout, + horizontalAlignment: self.textAlignment == .center ? .center : .left, + maximumNumberOfLines: 0, + highlightColor: highlightColor, + highlightAction: highlightAction, + tapAction: tapAction, + longTapAction: longTapAction + )), + environment: {}, + containerSize: CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: 1000000.0) + ) + } var backgroundFrame: CGRect @@ -941,6 +970,7 @@ public final class TooltipScreen: ViewController { case plain(text: String) case entities(text: String, entities: [MessageTextEntity]) case markdown(text: String) + case attributedString(text: NSAttributedString) } public class Action { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 82d88d8978..db5e176a26 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -772,13 +772,16 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } |> then(.single(.result(.story(peerId: peer.id, id: id))))) case .boost: - return .single(.progress) |> then(combineLatest( - context.engine.peers.getChannelBoostStatus(peerId: peer.id), - context.engine.peers.getMyBoostStatus() + return .single(.progress) + |> then( + combineLatest( + context.engine.peers.getChannelBoostStatus(peerId: peer.id), + context.engine.peers.getMyBoostStatus() + ) + |> map { boostStatus, myBoostStatus -> ResolveInternalUrlResult in + return .result(.boost(peerId: peer.id, status: boostStatus, myBoostStatus: myBoostStatus)) + } ) - |> map { boostStatus, myBoostStatus -> ResolveInternalUrlResult in - return .result(.boost(peerId: peer.id, status: boostStatus, myBoostStatus: myBoostStatus)) - }) } } else { return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index ff45af9c73..d3f5cc434d 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -248,7 +248,8 @@ public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> "section_bg_color": Int32(bitPattern: presentationTheme.list.itemBlocksBackgroundColor.rgb), "section_header_text_color": Int32(bitPattern: presentationTheme.list.freeTextColor.rgb), "subtitle_text_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb), - "destructive_text_color": Int32(bitPattern: presentationTheme.list.itemDestructiveColor.rgb) + "destructive_text_color": Int32(bitPattern: presentationTheme.list.itemDestructiveColor.rgb), + "section_separator_color": Int32(bitPattern: presentationTheme.list.itemBlocksSeparatorColor.rgb) ] }