diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 9c81fc27fde..01e2a67b4d2 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: macos-13 + runs-on: macos-14 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index da5a7dc22df..f2924022783 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: macos-13 + runs-on: macos-14 steps: - uses: actions/checkout@v2 diff --git a/Package.resolved b/Package.resolved index b8923f74b21..cbff87f4f96 100644 --- a/Package.resolved +++ b/Package.resolved @@ -42,7 +42,7 @@ "location" : "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", "state" : { "branch" : "develop", - "revision" : "f1b83c87b7c0da94adad36a17d36b124e98b86c7" + "revision" : "6e15969c39005fa75df34bb7bf0d77e433490d39" } }, { diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 6479b8a8026..71a76bbee88 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10860,6 +10860,7 @@ Sorry for the inconvenience."; "Chat.PanelStatusAuthorHidden" = "Senders of these messages restricted to link\ntheir name when forwarding."; "Chat.SavedMessagesChatsTooltip" = "Tap to view your Saved Messages organized by type or source"; +"Chat.ConfirmationDeleteFromSavedMessages" = "Delete from Saved Messages"; "Chat.ConfirmationRemoveFromSavedMessages" = "Remove from Saved Messages"; "Premium.MaxSavedPinsText" = "Sorry, you can't pin more than **%1$@** chats to the top. Unpin some that are currently pinned or subscribe to **Telegram Premium** to double the limit to **%2$@** chats."; @@ -10934,7 +10935,7 @@ Sorry for the inconvenience."; "Chat.ContextMenuTagsTitle" = "Tag the message with an emoji for quick access later"; -"Chat.ReactionContextMenu.FilterByTag" = "Fiter by Tag"; +"Chat.ReactionContextMenu.FilterByTag" = "Filter by Tag"; "Chat.ReactionContextMenu.RemoveTag" = "Remove Tag"; "Chat.EmptyStateMessagingRestrictedToPremium.Text" = "Subscribe to **Premium**\nto message **%@**."; @@ -10949,9 +10950,6 @@ Sorry for the inconvenience."; "Chat.SearchTagsPlaceholder" = "Search messages or tags"; -"Chat.TagSearchShowMessages" = "Show Other Messages"; -"Chat.TagSearchHideMessages" = "Hide Other Messages"; - "PrivacyInfo.UpgradeToPremium.Title" = "Upgrade to Premium"; "PrivacyInfo.UpgradeToPremium.ButtonTitle" = "Subscribe to Telegram Premium"; @@ -10966,9 +10964,80 @@ Sorry for the inconvenience."; "PrivacyInfo.ShowReadTime.PremiumInfo" = "Subscription will let you see **%@'s** read time without showing yours."; "PrivacyInfo.ShowReadTime.AlwaysToast.Text" = "Set **Last Seen** privacy to 'Nobody' or 'My Contacts.'"; +"Chat.ToastMessageTagged.Text" = "Message tagged with %@"; +"Chat.ToastMessagesTagged.Text" = "Messages tagged with %@"; +"Chat.ToastMessageTagged.Action" = "View"; + "Chat.PrivateMessageSeenTimestamp.Date" = "read %@"; "Chat.PrivateMessageSeenTimestamp.TodayAt" = "read today at %@"; "Chat.PrivateMessageSeenTimestamp.YesterdayAt" = "read yesterday at %@"; "Settings.Privacy.ReadTimePremiumActive" = "Subcribed to Telegram Premium"; "Settings.Privacy.ReadTimePremiumActiveFooter" = "Because you are a Telegram Premium subscriber, you will see the last seen and read time of all users who are sharing it with you – even if you are hiding yours."; + +"Privacy.VoiceMessages.NonPremiumHelp" = "Subscribe to Telegram Premium to restrict who can send you voice or video messages.\n\n[What is Telegram Premium?]()"; + +"ChatList.DeleteSavedPeerMyNotesConfirmation" = "Are you sure you want to delete all messages from %@?"; +"ChatList.DeleteSavedPeerMyNotesConfirmationTitle" = "My Notes"; + +"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"; + +"Premium.Stories.Quality.Title" = "Higher Quality"; +"Premium.Stories.Quality.Text" = "View video stories in double the resolution."; + +"Premium.MessageTags" = "Tags for Messages"; +"Premium.MessageTagsInfo" = "Organize your Saved Messages with tags for quicker access."; +"Premium.MessageTags.Proceed" = "About Telegram Premium"; + +"Chat.SavedMessagesTabInfoText" = "Messages you send to **Saved Messages**"; + +"VoiceOver.MessageSelectionButtonTag" = "Tag"; + +"Chat.ReactionSelectionTitleAddTag" = "Tag a message with emojis for quick search"; +"Chat.ReactionSelectionTitleEditTag" = "Edit tags of selected messages"; +"Chat.ForwardToSavedMessageTagSelectionTitle" = "You can add a tag to the message"; +"Chat.ForwardToSavedMessagesTagSelectionTitle" = "You can add a tag to messages"; + +"Chat.EditTagTitle.Placeholder" = "Name"; +"Chat.EditTagTitle.TitleSet" = "Set Name"; +"Chat.EditTagTitle.TitleEdit" = "Edit Name"; +"Chat.EditTagTitle.Text" = "You can label your emoji tag with a text name."; + +"Chat.SavedMessagesModeMenu.ViewAsChats" = "View as Chats"; +"Chat.SavedMessagesModeMenu.ViewAsMessages" = "View as Messages"; + +"Story.ContextMenuHD" = "Increase Quality"; +"Story.ContextMenuSD" = "Decrease Quality"; +"Story.ToastQualitySD.Title" = "Quality Lowered"; +"Story.ToastQualitySD.Text" = "Stories will now download faster."; +"Story.ToastQualityHD.Title" = "Quality Increased"; +"Story.ToastQualityHD.Text" = "You can lower the quality later for faster downloads."; + +"Story.UpgradeQuality.Title" = "High-Quality Stories"; +"Story.UpgradeQuality.Text" = "Subscribe to premium to view stories in higher resolution."; +"Story.UpgradeQuality.Action" = "Increase Quality"; +"Story.UpgradeQuality.ActionSubtitle" = "Premium Required"; + +"Chat.MessageContextMenu.NonPremiumTagsTitle" = "Organize your Saved Messages with tags for quicker access. [Learn more...]()"; + +"Chat.TooltipAddTagLabel" = "Tap and hold to add a name to your tag"; + +"Chat.MessageContextMenu.Remove" = "Remove"; + +"Chat.TagsHeaderPanel.Unlock" = "Unlock"; +"Chat.TagsHeaderPanel.AddTags" = "Add tags"; +"Chat.TagsHeaderPanel.AddTagsSuffix" = "to your Saved Messages"; +"Chat.ReactionContextMenu.SetTagLabel" = "Set Name"; +"Chat.ReactionContextMenu.EditTagLabel" = "Edit Name"; + +"Chat.BottomSearchPanel.MessageCountFormat" = "%1$@ %2$@"; +"Chat.BottomSearchPanel.MessageCount_1" = "message"; +"Chat.BottomSearchPanel.MessageCount_any" = "messages"; + +"Chat.BottomSearchPanel.DisplayModeFormat" = "Show as %@"; +"Chat.BottomSearchPanel.DisplayModeChat" = "Chat"; +"Chat.BottomSearchPanel.DisplayModeList" = "List"; diff --git a/build-system/GenerateStrings/GenerateStrings.py b/build-system/GenerateStrings/GenerateStrings.py index be3033ae34e..31741133a16 100644 --- a/build-system/GenerateStrings/GenerateStrings.py +++ b/build-system/GenerateStrings/GenerateStrings.py @@ -646,7 +646,9 @@ def generate(header_path: str, implementation_path: str, data_path: str, entries if entry.is_pluralized: argument_format_type = '' - if entry.positional_arguments[0].kind == 'd': + if len(entry.positional_arguments) == 0: + argument_format_type = '1' + elif entry.positional_arguments[0].kind == 'd': argument_format_type = '0' elif entry.positional_arguments[0].kind == '@': argument_format_type = '1' diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 55a59e69da9..d9cea8b1f81 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -161,12 +161,16 @@ public struct ChatAvailableMessageActions { public var banAuthor: Peer? public var disableDelete: Bool public var isCopyProtected: Bool + public var setTag: Bool + public var editTags: Set - public init(options: ChatAvailableMessageActionOptions, banAuthor: Peer?, disableDelete: Bool, isCopyProtected: Bool) { + public init(options: ChatAvailableMessageActionOptions, banAuthor: Peer?, disableDelete: Bool, isCopyProtected: Bool, setTag: Bool, editTags: Set) { self.options = options self.banAuthor = banAuthor self.disableDelete = disableDelete self.isCopyProtected = isCopyProtected + self.setTag = setTag + self.editTags = editTags } } @@ -356,6 +360,7 @@ public enum ChatSearchDomain: Equatable { case everything case members case member(Peer) + case tag(MessageReaction.Reaction) public static func ==(lhs: ChatSearchDomain, rhs: ChatSearchDomain) -> Bool { switch lhs { @@ -377,6 +382,12 @@ public enum ChatSearchDomain: Equatable { } else { return false } + case let .tag(reaction): + if case .tag(reaction) = rhs { + return true + } else { + return false + } } } } @@ -476,8 +487,9 @@ public final class NavigateToChatControllerParams { public let completion: (ChatController) -> Void public let chatListCompletion: ((ChatListController) -> Void)? public let pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)? + public let forceOpenChat: Bool - public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: Location, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: ChatControllerActivateInput? = nil, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, useBackAnimation: Bool = false, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)? = nil, completion: @escaping (ChatController) -> Void = { _ in }, chatListCompletion: @escaping (ChatListController) -> Void = { _ in }) { + public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: Location, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: ChatControllerActivateInput? = nil, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, useBackAnimation: Bool = false, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)? = nil, completion: @escaping (ChatController) -> Void = { _ in }, chatListCompletion: @escaping (ChatListController) -> Void = { _ in }, forceOpenChat: Bool = false) { self.navigationController = navigationController self.chatController = chatController self.chatLocationContextHolder = chatLocationContextHolder @@ -508,6 +520,7 @@ public final class NavigateToChatControllerParams { self.pushController = pushController self.completion = completion self.chatListCompletion = chatListCompletion + self.forceOpenChat = forceOpenChat } } @@ -930,7 +943,7 @@ public protocol SharedAccountContext: AnyObject { func openStorageUsage(context: AccountContext) func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController) func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) - func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set) -> Signal + func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, keepUpdated: Bool) -> Signal func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal func resolveUrlWithProgress(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 21c87ccda20..5c71fc3d861 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -41,6 +41,7 @@ public final class ChatMessageItemAssociatedData: Equatable { public let currentlyPlayingMessageId: EngineMessage.Index? public let isCopyProtectionEnabled: Bool public let availableReactions: AvailableReactions? + public let savedMessageTags: SavedMessageTags? public let defaultReaction: MessageReaction.Reaction? public let isPremium: Bool public let forceInlineReactions: Bool @@ -55,6 +56,7 @@ public final class ChatMessageItemAssociatedData: Equatable { public let chatThemes: [TelegramTheme] public let deviceContactsNumbers: Set public let isStandalone: Bool + public let isInline: Bool public init( automaticDownloadPeerType: MediaAutoDownloadPeerType, @@ -70,6 +72,7 @@ public final class ChatMessageItemAssociatedData: Equatable { currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, + savedMessageTags: SavedMessageTags?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, @@ -83,7 +86,8 @@ public final class ChatMessageItemAssociatedData: Equatable { audioTranscriptionTrial: AudioTranscription.TrialState = .defaultValue, chatThemes: [TelegramTheme] = [], deviceContactsNumbers: Set = Set(), - isStandalone: Bool = false + isStandalone: Bool = false, + isInline: Bool = false ) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadPeerId = automaticDownloadPeerId @@ -98,6 +102,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.currentlyPlayingMessageId = currentlyPlayingMessageId self.isCopyProtectionEnabled = isCopyProtectionEnabled self.availableReactions = availableReactions + self.savedMessageTags = savedMessageTags self.defaultReaction = defaultReaction self.isPremium = isPremium self.accountPeer = accountPeer @@ -112,6 +117,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.chatThemes = chatThemes self.deviceContactsNumbers = deviceContactsNumbers self.isStandalone = isStandalone + self.isInline = isInline } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { @@ -154,6 +160,9 @@ public final class ChatMessageItemAssociatedData: Equatable { if lhs.availableReactions != rhs.availableReactions { return false } + if lhs.savedMessageTags != rhs.savedMessageTags { + return false + } if lhs.isPremium != rhs.isPremium { return false } @@ -193,6 +202,9 @@ public final class ChatMessageItemAssociatedData: Equatable { if lhs.isStandalone != rhs.isStandalone { return false } + if lhs.isInline != rhs.isInline { + return false + } return true } } @@ -911,6 +923,24 @@ public extension Peer { } } +public struct ChatControllerCustomNavigationPanelNodeLayoutResult { + public var backgroundHeight: CGFloat + public var insetHeight: CGFloat + public var hitTestSlop: CGFloat + + public init(backgroundHeight: CGFloat, insetHeight: CGFloat, hitTestSlop: CGFloat) { + self.backgroundHeight = backgroundHeight + self.insetHeight = insetHeight + self.hitTestSlop = hitTestSlop + } +} + +public protocol ChatControllerCustomNavigationPanelNode: ASDisplayNode { + typealias LayoutResult = ChatControllerCustomNavigationPanelNodeLayoutResult + + func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, chatController: ChatController) -> LayoutResult +} + public protocol ChatController: ViewController { var chatLocation: ChatLocation { get } var canReadHistory: ValuePromise { get } @@ -918,9 +948,18 @@ public protocol ChatController: ViewController { var purposefulAction: (() -> Void)? { get set } + var stateUpdated: ((ContainedViewLayoutTransition) -> Void)? { get set } + var selectedMessageIds: Set? { get } var presentationInterfaceStateSignal: Signal { get } + var customNavigationBarContentNode: NavigationBarContentNode? { get } + var customNavigationPanelNode: ChatControllerCustomNavigationPanelNode? { get } + + var visibleContextController: ViewController? { get } + + var alwaysShowSearchResultsAsList: Bool { get set } + func updatePresentationMode(_ mode: ChatControllerPresentationMode) func beginMessageSearch(_ query: String) func displayPromoAnnouncement(text: String) diff --git a/submodules/AccountContext/Sources/OpenChatMessage.swift b/submodules/AccountContext/Sources/OpenChatMessage.swift index 94f055b3910..61d922255af 100644 --- a/submodules/AccountContext/Sources/OpenChatMessage.swift +++ b/submodules/AccountContext/Sources/OpenChatMessage.swift @@ -22,6 +22,7 @@ public final class OpenChatMessageParams { public let context: AccountContext public let updatedPresentationData: (initial: PresentationData, signal: Signal)? public let chatLocation: ChatLocation? + public let chatFilterTag: MemoryBuffer? public let chatLocationContextHolder: Atomic? public let message: Message public let standalone: Bool @@ -51,6 +52,7 @@ public final class OpenChatMessageParams { context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation?, + chatFilterTag: MemoryBuffer?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, @@ -79,6 +81,7 @@ public final class OpenChatMessageParams { self.context = context self.updatedPresentationData = updatedPresentationData self.chatLocation = chatLocation + self.chatFilterTag = chatFilterTag self.chatLocationContextHolder = chatLocationContextHolder self.message = message self.standalone = standalone diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index f4dbb4f557f..81991575034 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -30,12 +30,14 @@ public enum PremiumIntroSource { case storiesFormatting case storiesExpirationDurations case storiesSuggestedReactions + case storiesHigherQuality case channelBoost(EnginePeer.Id) case nameColor case similarChannels case wallpapers case presence case readTime + case messageTags } public enum PremiumGiftSource: Equatable { @@ -65,6 +67,7 @@ public enum PremiumDemoSubject { case stories case colors case wallpapers + case messageTags } public enum PremiumLimitSubject { diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index 8031e33178a..7de93b69d07 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -203,6 +203,8 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke public private(set) var currentFrameCount: Int = 0 private var playFromIndex: Int? + public var frameColorUpdated: ((UIColor) -> Void)? + private let timer = Atomic(value: nil) private let frameSource = Atomic?>(value: nil) @@ -525,6 +527,11 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke strongSelf.reportedStarted = true strongSelf.started() } + }, averageColor: strongSelf.frameColorUpdated == nil ? nil : { color in + guard let strongSelf = self else { + return + } + strongSelf.frameColorUpdated?(color) }) strongSelf.frameUpdated(frame.index, frame.totalFrames) @@ -635,6 +642,11 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke strongSelf.reportedStarted = true strongSelf.started() } + }, averageColor: strongSelf.frameColorUpdated == nil ? nil : { color in + guard let strongSelf = self else { + return + } + strongSelf.frameColorUpdated?(color) }) strongSelf.frameUpdated(frame.index, frame.totalFrames) @@ -790,6 +802,11 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke strongSelf.reportedStarted = true strongSelf.started() } + }, averageColor: strongSelf.frameColorUpdated == nil ? nil : { color in + guard let strongSelf = self else { + return + } + strongSelf.frameColorUpdated?(color) }) strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: false, duration: duration, timestamp: 0.0))) diff --git a/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift b/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift index 26f3efe1f62..953ede5bb06 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift @@ -51,7 +51,7 @@ final class AnimationRendererPool { protocol AnimationRenderer: ASDisplayNode { var currentFrameImage: UIImage? { get } - func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void) + func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void, averageColor: ((UIColor) -> Void)?) func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) } diff --git a/submodules/AnimatedStickerNode/Sources/CompressedAnimationRenderer.swift b/submodules/AnimatedStickerNode/Sources/CompressedAnimationRenderer.swift index 4fe044fa762..1f107f2737f 100644 --- a/submodules/AnimatedStickerNode/Sources/CompressedAnimationRenderer.swift +++ b/submodules/AnimatedStickerNode/Sources/CompressedAnimationRenderer.swift @@ -58,7 +58,7 @@ final class CompressedAnimationRenderer: ASDisplayNode, AnimationRenderer { self.layer.backgroundColor = nil } - func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void) { + func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void, averageColor: ((UIColor) -> Void)?) { switch type { case .dct: self.renderer.renderIdct(layer: self.layer as! MetalImageLayer, compressedImage: AnimationCompressor.CompressedImageData(data: data), completion: { [weak self] in diff --git a/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift b/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift index 57a1e49b06c..6b83ac9efb5 100644 --- a/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift +++ b/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift @@ -28,7 +28,7 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer { } } - func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void) { + func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void, averageColor: ((UIColor) -> Void)?) { assert(bytesPerRow > 0) let renderAsTemplateImage = self.renderAsTemplateImage queue.async { [weak self] in @@ -43,6 +43,7 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer { } var image: UIImage? + var averageColorValue: UIColor? autoreleasepool { image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData, contextBytesPerRow in @@ -83,6 +84,99 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer { if renderAsTemplateImage { image = image?.withRenderingMode(.alwaysTemplate) } + + if averageColor != nil { + let blurredWidth = 16 + let blurredHeight = 16 + let blurredBytesPerRow = blurredWidth * 4 + guard let context = DrawingContext(size: CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)), scale: 1.0, opaque: true, bytesPerRow: blurredBytesPerRow) else { + return + } + + let size = CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)) + + if let image, let cgImage = image.cgImage { + context.withFlippedContext { c in + c.setFillColor(UIColor.white.cgColor) + c.fill(CGRect(origin: CGPoint(), size: size)) + c.draw(cgImage, in: CGRect(origin: CGPoint(x: -size.width / 2.0, y: -size.height / 2.0), size: CGSize(width: size.width * 1.8, height: size.height * 1.8))) + } + } + + var destinationBuffer = vImage_Buffer() + destinationBuffer.width = UInt(blurredWidth) + destinationBuffer.height = UInt(blurredHeight) + destinationBuffer.data = context.bytes + destinationBuffer.rowBytes = context.bytesPerRow + + vImageBoxConvolve_ARGB8888(&destinationBuffer, + &destinationBuffer, + nil, + 0, 0, + UInt32(15), + UInt32(15), + nil, + vImage_Flags(kvImageTruncateKernel)) + + let divisor: Int32 = 0x1000 + + let rwgt: CGFloat = 0.3086 + let gwgt: CGFloat = 0.6094 + let bwgt: CGFloat = 0.0820 + + let adjustSaturation: CGFloat = 1.7 + + let a = (1.0 - adjustSaturation) * rwgt + adjustSaturation + let b = (1.0 - adjustSaturation) * rwgt + let c = (1.0 - adjustSaturation) * rwgt + let d = (1.0 - adjustSaturation) * gwgt + let e = (1.0 - adjustSaturation) * gwgt + adjustSaturation + let f = (1.0 - adjustSaturation) * gwgt + let g = (1.0 - adjustSaturation) * bwgt + let h = (1.0 - adjustSaturation) * bwgt + let i = (1.0 - adjustSaturation) * bwgt + adjustSaturation + + let satMatrix: [CGFloat] = [ + a, b, c, 0, + d, e, f, 0, + g, h, i, 0, + 0, 0, 0, 1 + ] + + var matrix: [Int16] = satMatrix.map { value in + return Int16(value * CGFloat(divisor)) + } + + vImageMatrixMultiply_ARGB8888(&destinationBuffer, &destinationBuffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile)) + + context.withFlippedContext { c in + c.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor) + c.fill(CGRect(origin: CGPoint(), size: size)) + } + + var sumR: UInt64 = 0 + var sumG: UInt64 = 0 + var sumB: UInt64 = 0 + var sumA: UInt64 = 0 + + for y in 0 ..< blurredHeight { + let row = context.bytes.assumingMemoryBound(to: UInt8.self).advanced(by: y * blurredBytesPerRow) + for x in 0 ..< blurredWidth { + let pixel = row.advanced(by: x * 4) + sumB += UInt64(pixel.advanced(by: 0).pointee) + sumG += UInt64(pixel.advanced(by: 1).pointee) + sumR += UInt64(pixel.advanced(by: 2).pointee) + sumA += UInt64(pixel.advanced(by: 3).pointee) + } + } + sumR /= UInt64(blurredWidth * blurredHeight) + sumG /= UInt64(blurredWidth * blurredHeight) + sumB /= UInt64(blurredWidth * blurredHeight) + sumA /= UInt64(blurredWidth * blurredHeight) + sumA = 255 + + averageColorValue = UIColor(red: CGFloat(sumR) / 255.0, green: CGFloat(sumG) / 255.0, blue: CGFloat(sumB) / 255.0, alpha: CGFloat(sumA) / 255.0) + } } Queue.mainQueue().async { @@ -100,6 +194,10 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer { strongSelf.highlightedContentNode?.frame = strongSelf.bounds } completion() + + if let averageColor, let averageColorValue { + averageColor(averageColorValue) + } } } } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 918647d5496..92093e6a9c2 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -767,6 +767,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { }, setupReplyMessage: { _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in + }, cancelMessageSelection: { _ in }, deleteSelectedMessages: { }, reportSelectedMessages: { }, reportMessages: { _, _ in @@ -985,6 +986,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { }, updateHistoryFilter: { _ in + }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in }, chatController: { return nil diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD index 45c1202ce3e..d9b8a46d378 100644 --- a/submodules/AuthorizationUI/BUILD +++ b/submodules/AuthorizationUI/BUILD @@ -45,6 +45,8 @@ swift_library( "//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/AlertUI:AlertUI", "//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities", + "//submodules/MoreButtonNode:MoreButtonNode", + "//submodules/ContextUI:ContextUI", ], visibility = [ "//visibility:public", diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index 7d1c2c6ad3a..bdc89f11928 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -1037,7 +1037,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } self.openUrl(url) } - controller.signUpWithName = { [weak self, weak controller] firstName, lastName, avatarData, avatarAsset, avatarAdjustments in + controller.signUpWithName = { [weak self, weak controller] firstName, lastName, avatarData, avatarAsset, avatarAdjustments, announceSignUp in if let strongSelf = self { controller?.inProgress = true @@ -1097,7 +1097,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth avatarVideo = nil } - strongSelf.actionDisposable.set((signUpWithName(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, firstName: firstName, lastName: lastName, avatarData: avatarData, avatarVideo: avatarVideo, videoStartTimestamp: videoStartTimestamp, forcedPasswordSetupNotice: { value in + strongSelf.actionDisposable.set((signUpWithName(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, firstName: firstName, lastName: lastName, avatarData: avatarData, avatarVideo: avatarVideo, videoStartTimestamp: videoStartTimestamp, disableJoinNotifications: !announceSignUp, forcedPasswordSetupNotice: { value in guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else { return nil } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift index 02154b68c29..fda1ef98551 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift @@ -11,6 +11,8 @@ import ImageCompression import LegacyMediaPickerUI import Postbox import TextFormat +import MoreButtonNode +import ContextUI final class AuthorizationSequenceSignUpController: ViewController { private var controllerNode: AuthorizationSequenceSignUpControllerNode { @@ -19,18 +21,22 @@ final class AuthorizationSequenceSignUpController: ViewController { private var validLayout: ContainerViewLayout? + private let moreButtonNode: MoreButtonNode + private let presentationData: PresentationData private let back: () -> Void var initialName: (String, String) = ("", "") private var termsOfService: UnauthorizedAccountTermsOfService? - var signUpWithName: ((String, String, Data?, Any?, TGVideoEditAdjustments?) -> Void)? + var signUpWithName: ((String, String, Data?, Any?, TGVideoEditAdjustments?, Bool) -> Void)? var openUrl: ((String) -> Void)? var avatarAsset: Any? var avatarAdjustments: TGVideoEditAdjustments? + var announceSignUp = true + private let hapticFeedback = HapticFeedback() var inProgress: Bool = false { @@ -44,6 +50,9 @@ final class AuthorizationSequenceSignUpController: ViewController { self.presentationData = presentationData self.back = back + self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme) + self.moreButtonNode.iconNode.enqueueState(.more, animated: false) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) @@ -68,6 +77,14 @@ final class AuthorizationSequenceSignUpController: ViewController { if displayCancel { self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) } + + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + + self.moreButtonNode.action = { [weak self] _, gesture in + if let strongSelf = self { + strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture) + } + } } required init(coder aDecoder: NSCoder) { @@ -81,6 +98,46 @@ final class AuthorizationSequenceSignUpController: ViewController { })]), in: .window(.root)) } + @objc private func moreButtonPressed() { + self.moreButtonNode.buttonPressed() + } + + @objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { + let presentationData = self.presentationData + + let announceSignUp = self.announceSignUp + var items: [ContextMenuItem] = [] + + let nop: ((ContextMenuActionItem.Action) -> Void)? = nil + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Login_Announce_Info, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: nop))) + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Login_Announce_Notify, icon: { theme in + if !announceSignUp { + return nil + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { [weak self] _, a in + a(.default) + + self?.announceSignUp = true + }))) + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Login_Announce_DontNotify, icon: { theme in + if announceSignUp { + return nil + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { [weak self] _, a in + a(.default) + + self?.announceSignUp = false + }))) + + + let contextController = ContextController(presentationData: self.presentationData, source: .reference(AuthorizationContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + self.present(contextController, in: .window(.root)) + } + func updateNavigationItems() { guard let layout = self.validLayout, layout.size.width < 360.0 else { return @@ -211,7 +268,21 @@ final class AuthorizationSequenceSignUpController: ViewController { let result = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) TempBox.shared.dispose(tempFile) return result - }), self.avatarAsset, self.avatarAdjustments) + }), self.avatarAsset, self.avatarAdjustments, self.announceSignUp) } } } + +private final class AuthorizationContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceNode: ContextReferenceContentNode + + init(controller: ViewController, sourceNode: ContextReferenceContentNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/Camera/BUILD b/submodules/Camera/BUILD index 07377c40c16..19fec724ada 100644 --- a/submodules/Camera/BUILD +++ b/submodules/Camera/BUILD @@ -40,6 +40,10 @@ apple_resource_bundle( ], ) +NGDEPS = [ + "//Nicegram/NGData:NGData", +] + swift_library( name = "Camera", module_name = "Camera", @@ -52,7 +56,7 @@ swift_library( data = [ ":CameraBundle", ], - deps = [ + deps = NGDEPS + [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 609f9c2608d..9d0c54d93c2 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -85,7 +85,7 @@ final class CameraDeviceContext { } private func maxDimensions(additional: Bool, preferWide: Bool) -> CMVideoDimensions { - if self.isRoundVideo && !Camera.isDualCameraSupported { + if self.isRoundVideo && self.exclusive { return CMVideoDimensions(width: 640, height: 480) } else { if additional || preferWide { diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index ab889bf248b..7bbaa239035 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -1,3 +1,6 @@ +// MARK: Nicegram (useRearCamTelescopy) +import NGData +// import Foundation import AVFoundation import UIKit @@ -95,7 +98,9 @@ final class CameraOutput: NSObject { private var roundVideoFilter: CameraRoundVideoFilter? private let semaphore = DispatchSemaphore(value: 1) - private let queue = DispatchQueue(label: "") + private let videoQueue = DispatchQueue(label: "", qos: .userInitiated) + private let audioQueue = DispatchQueue(label: "") + private let metadataQueue = DispatchQueue(label: "") private var photoCaptureRequests: [Int64: PhotoCaptureContext] = [:] @@ -114,7 +119,7 @@ final class CameraOutput: NSObject { self.isVideoMessage = use32BGRA super.init() - + if #available(iOS 13.0, *) { self.photoOutput.maxPhotoQualityPrioritization = .balanced } @@ -135,13 +140,13 @@ final class CameraOutput: NSObject { } else { session.session.addOutput(self.videoOutput) } - self.videoOutput.setSampleBufferDelegate(self, queue: self.queue) + self.videoOutput.setSampleBufferDelegate(self, queue: self.videoQueue) } else { Logger.shared.log("Camera", "Can't add video output") } if audio, session.session.canAddOutput(self.audioOutput) { session.session.addOutput(self.audioOutput) - self.audioOutput.setSampleBufferDelegate(self, queue: self.queue) + self.audioOutput.setSampleBufferDelegate(self, queue: self.audioQueue) } if photo, session.session.canAddOutput(self.photoOutput) { if session.hasMultiCam { @@ -305,6 +310,15 @@ final class CameraOutput: NSObject { return .complete() } + Logger.shared.log("CameraOutput", "startRecording") + + // MARK: Nicegram (useRearCamTelescopy) + if case .roundVideo = mode, + NGSettings.useRearCamTelescopy { + self.currentPosition = .back + } + // + self.currentMode = mode self.lastSampleTimestamp = nil self.captureOrientation = orientation @@ -449,18 +463,19 @@ final class CameraOutput: NSObject { transitionFactor = 1.0 - max(0.0, (currentTimestamp - self.lastSwitchTimestamp) / duration) } } - if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { - let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) - if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { - - } else { - if (transitionFactor == 1.0 && fromAdditionalOutput) || (transitionFactor == 0.0 && !fromAdditionalOutput) || (transitionFactor > 0.0 && transitionFactor < 1.0) { + + if (transitionFactor == 1.0 && fromAdditionalOutput) + || (transitionFactor == 0.0 && !fromAdditionalOutput) + || (transitionFactor > 0.0 && transitionFactor < 1.0) { + if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { + let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) + if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { + + } else { videoRecorder.appendSampleBuffer(processedSampleBuffer) self.lastSampleTimestamp = presentationTime } } - } else { - videoRecorder.appendSampleBuffer(sampleBuffer) } } else { var additional = self.currentPosition == .front @@ -518,7 +533,7 @@ final class CameraOutput: NSObject { return nil } self.semaphore.wait() - + let mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription) let extensions = CMFormatDescriptionGetExtensions(formatDescription) as! [String: Any] @@ -528,6 +543,7 @@ final class CameraOutput: NSObject { var newFormatDescription: CMFormatDescription? var status = CMVideoFormatDescriptionCreate(allocator: nil, codecType: mediaSubType, width: videoMessageDimensions.width, height: videoMessageDimensions.height, extensions: updatedExtensions as CFDictionary, formatDescriptionOut: &newFormatDescription) guard status == noErr, let newFormatDescription else { + self.semaphore.signal() return nil } @@ -539,8 +555,9 @@ final class CameraOutput: NSObject { self.roundVideoFilter = filter } if !filter.isPrepared { - filter.prepare(with: newFormatDescription, outputRetainedBufferCountHint: 3) + filter.prepare(with: newFormatDescription, outputRetainedBufferCountHint: 4) } + guard let newPixelBuffer = filter.render(pixelBuffer: videoPixelBuffer, additional: additional, captureOrientation: self.captureOrientation, transitionFactor: transitionFactor) else { self.semaphore.signal() return nil @@ -592,7 +609,7 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA guard CMSampleBufferDataIsReady(sampleBuffer) else { return } - + if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection) } else { @@ -607,7 +624,9 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA } func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - + if #available(iOS 13.0, *) { + Logger.shared.log("VideoRecorder", "Dropped sample buffer \(sampleBuffer.attachments)") + } } } diff --git a/submodules/Camera/Sources/VideoRecorder.swift b/submodules/Camera/Sources/VideoRecorder.swift index 1d5f9352cc4..e785db18348 100644 --- a/submodules/Camera/Sources/VideoRecorder.swift +++ b/submodules/Camera/Sources/VideoRecorder.swift @@ -40,11 +40,12 @@ private final class VideoRecorderImpl { private var pendingAudioSampleBuffers: [CMSampleBuffer] = [] - private var _duration: CMTime = .zero + private var _duration = Atomic(value: .zero) public var duration: CMTime { - self.queue.sync { _duration } + return self._duration.with { $0 } } + private var startedSession = false private var lastVideoSampleTime: CMTime = .invalid private var recordingStartSampleTime: CMTime = .invalid private var recordingStopSampleTime: CMTime = .invalid @@ -59,7 +60,11 @@ private final class VideoRecorderImpl { private let error = Atomic(value: nil) - private var stopped = false + private var _stopped = Atomic(value: false) + private var stopped: Bool { + return self._stopped.with { $0 } + } + private var hasAllVideoBuffers = false private var hasAllAudioBuffers = false @@ -113,20 +118,21 @@ private final class VideoRecorderImpl { } } + + private var previousPresentationTime: Double? + private var previousAppendTime: Double? + public func appendVideoSampleBuffer(_ sampleBuffer: CMSampleBuffer) { - if let _ = self.hasError() { - return - } - - guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Video else { - return - } - - let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) self.queue.async { - guard !self.stopped && self.error.with({ $0 }) == nil else { + guard self.hasError() == nil && !self.stopped else { return } + + guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Video else { + return + } + let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + var failed = false if self.videoInput == nil { Logger.shared.log("VideoRecorder", "Try adding video input") @@ -159,36 +165,56 @@ private final class VideoRecorderImpl { return } if self.videoInput != nil && (self.audioInput != nil || !self.configuration.hasAudio) { + print("startWriting") + let start = CACurrentMediaTime() if !self.assetWriter.startWriting() { if let error = self.assetWriter.error { self.transitionToFailedStatus(error: .avError(error)) - return } } - - self.assetWriter.startSession(atSourceTime: presentationTime) - self.recordingStartSampleTime = presentationTime - self.lastVideoSampleTime = presentationTime + print("started In \(CACurrentMediaTime() - start)") + return } + } else if self.assetWriter.status == .writing && !self.startedSession { + print("Started session at \(presentationTime)") + self.assetWriter.startSession(atSourceTime: presentationTime) + self.recordingStartSampleTime = presentationTime + self.lastVideoSampleTime = presentationTime + self.startedSession = true } if self.recordingStartSampleTime == .invalid || sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } - if self.assetWriter.status == .writing { + if self.assetWriter.status == .writing && self.startedSession { if self.recordingStopSampleTime != .invalid && sampleBuffer.presentationTimestamp > self.recordingStopSampleTime { self.hasAllVideoBuffers = true self.maybeFinish() return } - - if let videoInput = self.videoInput, videoInput.isReadyForMoreMediaData { + + if let videoInput = self.videoInput { + while (!videoInput.isReadyForMoreMediaData) + { + let maxDate = Date(timeIntervalSinceNow: 0.05) + RunLoop.current.run(until: maxDate) + } + } + + if let videoInput = self.videoInput { + let time = CACurrentMediaTime() + if let previousPresentationTime = self.previousPresentationTime, let previousAppendTime = self.previousAppendTime { + print("appending \(presentationTime.seconds) (\(presentationTime.seconds - previousPresentationTime) ) on \(time) (\(time - previousAppendTime)") + } + self.previousPresentationTime = presentationTime.seconds + self.previousAppendTime = time + if videoInput.append(sampleBuffer) { self.lastVideoSampleTime = presentationTime let startTime = self.recordingStartSampleTime let duration = presentationTime - startTime - self._duration = duration + let _ = self._duration.modify { _ in return duration } } if !self.savedTransitionImage, let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { @@ -220,16 +246,12 @@ private final class VideoRecorderImpl { } public func appendAudioSampleBuffer(_ sampleBuffer: CMSampleBuffer) { - if let _ = self.hasError() { - return - } - - guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Audio else { - return - } - self.queue.async { - guard !self.stopped && self.error.with({ $0 }) == nil else { + guard self.hasError() == nil && !self.stopped else { + return + } + + guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Audio else { return } @@ -274,7 +296,7 @@ private final class VideoRecorderImpl { return } - if self.recordingStartSampleTime != .invalid { //self.assetWriter.status == .writing { + if self.recordingStartSampleTime != .invalid { if sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } @@ -304,7 +326,7 @@ private final class VideoRecorderImpl { } return } - self.stopped = true + let _ = self._stopped.modify { _ in return true } self.pendingAudioSampleBuffers = [] if self.assetWriter.status == .writing { self.assetWriter.cancelWriting() @@ -318,7 +340,7 @@ private final class VideoRecorderImpl { } public var isRecording: Bool { - self.queue.sync { !(self.hasAllVideoBuffers && self.hasAllAudioBuffers) } + return !self.stopped } public func stopRecording() { @@ -334,60 +356,58 @@ private final class VideoRecorderImpl { } } - public func maybeFinish() { - self.queue.async { - guard self.hasAllVideoBuffers && self.hasAllVideoBuffers else { - return - } - self.stopped = true - self.finish() + private func maybeFinish() { + dispatchPrecondition(condition: .onQueue(self.queue)) + guard self.hasAllVideoBuffers && self.hasAllVideoBuffers && !self.stopped else { + return } + let _ = self._stopped.modify { _ in return true } + self.finish() } - public func finish() { - self.queue.async { - let completion = self.completion - if self.recordingStopSampleTime == .invalid { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + private func finish() { + dispatchPrecondition(condition: .onQueue(self.queue)) + let completion = self.completion + if self.recordingStopSampleTime == .invalid { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if let _ = self.error.with({ $0 }) { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + return + } + + if let _ = self.error.with({ $0 }) { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if !self.tryAppendingPendingAudioBuffers() { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + return + } + + if !self.tryAppendingPendingAudioBuffers() { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if self.assetWriter.status == .writing { - self.assetWriter.finishWriting { - if let _ = self.assetWriter.error { - DispatchQueue.main.async { - completion(false, nil, nil) - } - } else { - DispatchQueue.main.async { - completion(true, self.transitionImage, self.positionChangeTimestamps) - } + return + } + + if self.assetWriter.status == .writing { + self.assetWriter.finishWriting { + if let _ = self.assetWriter.error { + DispatchQueue.main.async { + completion(false, nil, nil) + } + } else { + DispatchQueue.main.async { + completion(true, self.transitionImage, self.positionChangeTimestamps) } } - } else if let _ = self.assetWriter.error { - DispatchQueue.main.async { - completion(false, nil, nil) - } - } else { - DispatchQueue.main.async { - completion(false, nil, nil) - } + } + } else if let _ = self.assetWriter.error { + DispatchQueue.main.async { + completion(false, nil, nil) + } + } else { + DispatchQueue.main.async { + completion(false, nil, nil) } } } @@ -423,7 +443,13 @@ private final class VideoRecorderImpl { } private func internalAppendAudioSampleBuffer(_ sampleBuffer: CMSampleBuffer) -> Bool { - if let audioInput = self.audioInput, audioInput.isReadyForMoreMediaData { + if self.startedSession, let audioInput = self.audioInput { + while (!audioInput.isReadyForMoreMediaData) + { + let maxDate = Date(timeIntervalSinceNow: 0.05) + RunLoop.current.run(until: maxDate) + } + if !audioInput.append(sampleBuffer) { if let _ = self.assetWriter.error { return false diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index d5cc4df11d6..c717cc45e32 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -925,7 +925,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: } } -public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, parentController: ViewController) -> Signal<[ContextMenuItem], NoError> { +public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, parentController: ViewController, deletePeerChat: @escaping (EnginePeer.Id) -> Void) -> Signal<[ContextMenuItem], NoError> { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let strings = presentationData.strings @@ -959,6 +959,11 @@ public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, }) }))) + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in + deletePeerChat(PeerId(threadId)) + f(.default) + }))) + return .single(items) } } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 036485fcb0d..2f0995b1ec8 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1940,8 +1940,24 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } |> distinctUntilChanged + let preferHighQualityStories: Signal = combineLatest( + context.sharedContext.automaticMediaDownloadSettings + |> map { settings in + return settings.highQualityStories + } + |> distinctUntilChanged, + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) + ) + ) + |> map { setting, peer -> Bool in + let isPremium = peer?.isPremium ?? false + return setting && isPremium + } + |> distinctUntilChanged + self.preloadStorySubscriptionsDisposable = (combineLatest(queue: .mainQueue(), - self.context.engine.messages.preloadStorySubscriptions(isHidden: self.location == .chatList(groupId: .archive)), + self.context.engine.messages.preloadStorySubscriptions(isHidden: self.location == .chatList(groupId: .archive), preferHighQuality: preferHighQualityStories), self.context.sharedContext.automaticMediaDownloadSettings, automaticDownloadNetworkType ) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 502b13263e3..182b7285ec9 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -1156,7 +1156,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private func mediaMessageContextAction(_ message: EngineMessage, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) { let gesture: ContextGesture? = anyRecognizer as? ContextGesture - let _ = (chatMediaListPreviewControllerData(context: self.context, chatLocation: .peer(id: message.id.peerId), chatLocationContextHolder: Atomic(value: nil), message: message._asMessage(), standalone: true, reverseMessageGalleryOrder: false, navigationController: self.navigationController) + let _ = (chatMediaListPreviewControllerData(context: self.context, chatLocation: .peer(id: message.id.peerId), chatFilterTag: nil, chatLocationContextHolder: Atomic(value: nil), message: message._asMessage(), standalone: true, reverseMessageGalleryOrder: false, navigationController: self.navigationController) |> deliverOnMainQueue).startStandalone(next: { [weak self] previewData in guard let strongSelf = self else { gesture?.cancel() @@ -1460,7 +1460,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo guard let navigationController = self.navigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index c43de99600a..33924499f7d 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -881,7 +881,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: requiresPremiumForMessaging + requiresPremiumForMessaging: requiresPremiumForMessaging, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): @@ -2200,7 +2201,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } openMediaMessageImpl = { message, mode in - let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: { + let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: { interaction.dismissInput() }, present: { c, a in interaction.present(c, a) @@ -2353,7 +2354,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }) } - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: .peer(id: message.id.peerId), chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: { + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: .peer(id: message.id.peerId), chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: { interaction.dismissInput() }, present: { c, a in interaction.present(c, a) @@ -3743,7 +3744,8 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) case .media: return nil diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index 53f89b3deaa..865bdef36e8 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -208,7 +208,8 @@ public final class ChatListShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index eaeb203047a..7efccf61939 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -101,6 +101,7 @@ public enum ChatListItemContent { public var autoremoveTimeout: Int32? public var storyState: StoryState? public var requiresPremiumForMessaging: Bool + public var displayAsTopicList: Bool public init( messages: [EngineMessage], @@ -121,7 +122,8 @@ public enum ChatListItemContent { topForumTopicItems: [EngineChatList.ForumTopicData], autoremoveTimeout: Int32?, storyState: StoryState?, - requiresPremiumForMessaging: Bool + requiresPremiumForMessaging: Bool, + displayAsTopicList: Bool ) { self.messages = messages self.peer = peer @@ -142,6 +144,7 @@ public enum ChatListItemContent { self.autoremoveTimeout = autoremoveTimeout self.storyState = storyState self.requiresPremiumForMessaging = requiresPremiumForMessaging + self.displayAsTopicList = displayAsTopicList } } @@ -1454,11 +1457,17 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else if peer.isDeleted { overrideImage = .deletedIcon } - var isForum = false + var isForumAvatar = false if case let .channel(channel) = peer, channel.flags.contains(.isForum) { - isForum = true + isForumAvatar = true } - self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForum ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0)) + if case let .peer(data) = item.content { + if data.displayAsTopicList { + isForumAvatar = true + } + } + + self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForumAvatar ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0)) if peer.isPremium && peer.id != item.context.account.peerId { let context = item.context @@ -1972,6 +1981,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var displayForwardedIcon = false var displayStoryReplyIcon = false + var ignoreForwardedIcon = false switch contentData { case let .chat(itemPeer, _, _, _, text, spoilers, customEmojiRanges): @@ -2019,6 +2029,29 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } + if case .chatList = item.chatListLocation, itemPeer.peerId == item.context.account.peerId, let message = messages.first { + var effectiveAuthor: Peer? = message.author?._asPeer() + if let forwardInfo = message.forwardInfo { + effectiveAuthor = forwardInfo.author + if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { + effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil) + } + } + if let sourceAuthorInfo = message._asMessage().sourceAuthorInfo { + if let originalAuthor = sourceAuthorInfo.originalAuthor, let peer = message.peers[originalAuthor] { + effectiveAuthor = peer + } else if let authorSignature = sourceAuthorInfo.originalAuthorName { + effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil) + } + } + + if let effectiveAuthor, effectiveAuthor.id != itemPeer.chatMainPeer?.id { + authorIsCurrentChat = false + peerText = EnginePeer(effectiveAuthor).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + ignoreForwardedIcon = true + } + } + if let _ = peerText, case let .channel(channel) = itemPeer.chatMainPeer, channel.flags.contains(.isForum), threadInfo == nil { if let forumTopicData = forumTopicData { forumThread = (forumTopicData.id, forumTopicData.title, forumTopicData.iconFileId, forumTopicData.iconColor, forumTopicData.isUnread) @@ -2228,12 +2261,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { attributedText = composedString - if case .savedMessagesChats = item.chatListLocation { - displayForwardedIcon = false - } else if let forwardInfo = message.forwardInfo, !forwardInfo.flags.contains(.isImported) { - displayForwardedIcon = true - } else if let _ = message.attributes.first(where: { $0 is ReplyStoryAttribute }) { - displayStoryReplyIcon = true + if !ignoreForwardedIcon { + if case .savedMessagesChats = item.chatListLocation { + displayForwardedIcon = false + } else if let forwardInfo = message.forwardInfo, !forwardInfo.flags.contains(.isImported) { + displayForwardedIcon = true + } else if let _ = message.attributes.first(where: { $0 is ReplyStoryAttribute }) { + displayStoryReplyIcon = true + } } var displayMediaPreviews = true @@ -2581,7 +2616,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { switch item.content { case let .peer(peerData): if let peer = peerData.messages.last?.author { - if peer.isScam { + if case .savedMessagesChats = item.chatListLocation, peer.id == item.context.account.peerId { + currentCredibilityIconContent = nil + } else if peer.isScam { currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) } else if peer.isFake { currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) @@ -2601,7 +2638,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { break } } else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer { - if peer.isScam { + if case .savedMessagesChats = item.chatListLocation, peer.id == item.context.account.peerId { + currentCredibilityIconContent = nil + } else if peer.isScam { currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) } else if peer.isFake { currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 7049dbd1e66..4173eca26d5 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -453,7 +453,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL hasUnseenCloseFriends: storyState.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging + requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, + displayAsTopicList: peerEntry.displayAsTopicList )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -825,7 +826,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL hasUnseenCloseFriends: storyState.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging + requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, + displayAsTopicList: peerEntry.displayAsTopicList )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -1307,6 +1309,8 @@ public final class ChatListNode: ListView { public let isMainTab = ValuePromise(false, ignoreRepeated: true) private let suggestedChatListNotice = Promise(nil) + public var synchronousDrawingWhenNotAnimated: Bool = false + public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool, autoSetReady: Bool, isMainTab: Bool?) { self.context = context self.location = location @@ -1471,10 +1475,16 @@ public final class ChatListNode: ListView { } switch error { case let .limitReached(count): + var replaceImpl: ((ViewController) -> Void)? let controller = PremiumLimitScreen(context: context, subject: .pinnedSavedPeers, count: Int32(count), action: { + let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats) + replaceImpl?(premiumScreen) return true }) self.push?(controller) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } default: break } @@ -2010,39 +2020,6 @@ public final class ChatListNode: ListView { } let currentPeerId: EnginePeer.Id = context.account.peerId - - /*let contactList: Signal - if case let .chatList(appendContacts) = mode, appendContacts { - contactList = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Contacts.List(includePresences: true)) - |> map(Optional.init) - } else { - contactList = .single(nil) - } - let _ = contactList*/ - - - /*let emptyInitialView = ChatListNodeView( - originalList: EngineChatList( - items: [], - groupItems: [], - additionalItems: [], - hasEarlier: false, - hasLater: false, - isLoading: false - ), - filteredEntries: [ChatListNodeEntry.HeaderEntry], - isLoading: false, - filter: nil - ) - let _ = previousView.swap(emptyInitialView) - - let _ = (preparedChatListNodeViewTransition(from: nil, to: emptyInitialView, reason: .initial, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: nil, searchMode: false) - |> map { mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: nil, mode: mode, isPeerEnabled: nil, transition: $0) }).start(next: { [weak self] value in - guard let self else { - return - } - let _ = self.enqueueTransition(value).start() - })*/ let contacts: Signal<[ChatListContactPeer], NoError> if case .chatList(groupId: .root) = location, chatListFilter == nil, case .chatList = mode { @@ -3502,7 +3479,7 @@ public final class ChatListNode: ListView { var options = transition.options //options.insert(.Synchronous) - if self.view.window != nil { + if self.view.window != nil || self.synchronousDrawingWhenNotAnimated { if !options.contains(.AnimateInsertion) { options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousResourceLoading) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 1083c3c0c2f..b84da8583c4 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -124,6 +124,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { var revealed: Bool var storyState: ChatListNodeState.StoryState? var requiresPremiumForMessaging: Bool + var displayAsTopicList: Bool init( // MARK: Nicegram PinnedChats @@ -152,7 +153,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { topForumTopicItems: [EngineChatList.ForumTopicData], revealed: Bool, storyState: ChatListNodeState.StoryState?, - requiresPremiumForMessaging: Bool + requiresPremiumForMessaging: Bool, + displayAsTopicList: Bool ) { // MARK: Nicegram PinnedChats self.nicegramItem = nicegramItem @@ -181,6 +183,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { self.revealed = revealed self.storyState = storyState self.requiresPremiumForMessaging = requiresPremiumForMessaging + self.displayAsTopicList = displayAsTopicList } static func ==(lhs: PeerEntryData, rhs: PeerEntryData) -> Bool { @@ -296,6 +299,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhs.requiresPremiumForMessaging != rhs.requiresPremiumForMessaging { return false } + if lhs.displayAsTopicList != rhs.displayAsTopicList { + return false + } return true } } @@ -730,7 +736,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: entry.displayAsTopicList )) if let threadInfo, threadInfo.isHidden { @@ -785,7 +792,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, topForumTopicItems: [], revealed: false, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false ))) if foundPinningIndex != 0 { foundPinningIndex -= 1 @@ -817,7 +825,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, topForumTopicItems: [], revealed: false, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false ))) } else { if !filteredAdditionalItemEntries.isEmpty { @@ -869,7 +878,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, topForumTopicItems: item.item.topForumTopicItems, revealed: state.hiddenItemShouldBeTemporaryRevealed || state.editing, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false ))) if pinningIndex != 0 { pinningIndex -= 1 @@ -918,7 +928,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, topForumTopicItems: [], revealed: false, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false ))) if pinningIndex != 0 { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 1e7e66a1987..dd0101c7274 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -114,6 +114,8 @@ public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: E } func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account) -> Signal { + let accountPeerId = account.peerId + switch chatListLocation { case let .chatList(groupId): let filterPredicate: ChatListFilterPredicate? @@ -129,7 +131,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat signal = account.viewTracker.tailChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, count: count) return signal |> map { view, updateType -> ChatListNodeViewUpdate in - return ChatListNodeViewUpdate(list: EngineChatList(view), type: updateType, scrollPosition: nil) + return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: updateType, scrollPosition: nil) } case let .navigation(index, _): guard case let .chatList(index) = index else { @@ -145,7 +147,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat } else { genericType = updateType } - return ChatListNodeViewUpdate(list: EngineChatList(view), type: genericType, scrollPosition: nil) + return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: genericType, scrollPosition: nil) } case let .scroll(index, sourceIndex, scrollPosition, animated, _): guard case let .chatList(index) = index else { @@ -165,7 +167,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat } else { genericType = updateType } - return ChatListNodeViewUpdate(list: EngineChatList(view), type: genericType, scrollPosition: scrollPosition) + return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: genericType, scrollPosition: scrollPosition) } } case let .forum(peerId): @@ -292,7 +294,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat hasFailed: false, isContact: false, autoremoveTimeout: nil, - storyStats: nil + storyStats: nil, + displayAsTopicList: false )) } @@ -356,7 +359,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat hasFailed: false, isContact: false, autoremoveTimeout: nil, - storyStats: nil + storyStats: nil, + displayAsTopicList: false )) } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index d9f1bbcbe34..29746a47a05 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -79,6 +79,7 @@ public final class ChatPanelInterfaceInteraction { public let setupReplyMessage: (MessageId?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void public let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void public let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void + public let cancelMessageSelection: (ContainedViewLayoutTransition) -> Void public let deleteSelectedMessages: () -> Void public let reportSelectedMessages: () -> Void public let reportMessages: ([Message], ContextControllerProtocol?) -> Void @@ -180,6 +181,7 @@ public final class ChatPanelInterfaceInteraction { public let openPremiumGift: () -> Void public let openPremiumRequiredForMessaging: () -> Void public let updateHistoryFilter: ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void + public let updateDisplayHistoryFilterAsList: (Bool) -> Void public let requestLayout: (ContainedViewLayoutTransition) -> Void public let chatController: () -> ViewController? public let statuses: ChatPanelInterfaceInteractionStatuses? @@ -195,6 +197,7 @@ public final class ChatPanelInterfaceInteraction { setupReplyMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void, setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, + cancelMessageSelection: @escaping (ContainedViewLayoutTransition) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message], ContextControllerProtocol?) -> Void, @@ -296,6 +299,7 @@ public final class ChatPanelInterfaceInteraction { openPremiumGift: @escaping () -> Void, openPremiumRequiredForMessaging: @escaping () -> Void, updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void, + updateDisplayHistoryFilterAsList: @escaping (Bool) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, chatController: @escaping () -> ViewController?, statuses: ChatPanelInterfaceInteractionStatuses? @@ -310,6 +314,7 @@ public final class ChatPanelInterfaceInteraction { self.setupReplyMessage = setupReplyMessage self.setupEditMessage = setupEditMessage self.beginMessageSelection = beginMessageSelection + self.cancelMessageSelection = cancelMessageSelection self.deleteSelectedMessages = deleteSelectedMessages self.reportSelectedMessages = reportSelectedMessages self.reportMessages = reportMessages @@ -411,6 +416,7 @@ public final class ChatPanelInterfaceInteraction { self.openPremiumGift = openPremiumGift self.openPremiumRequiredForMessaging = openPremiumRequiredForMessaging self.updateHistoryFilter = updateHistoryFilter + self.updateDisplayHistoryFilterAsList = updateDisplayHistoryFilterAsList self.requestLayout = requestLayout self.chatController = chatController @@ -429,6 +435,7 @@ public final class ChatPanelInterfaceInteraction { }, setupReplyMessage: { _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in + }, cancelMessageSelection: { _ in }, deleteSelectedMessages: { }, reportSelectedMessages: { }, reportMessages: { _, _ in @@ -531,6 +538,7 @@ public final class ChatPanelInterfaceInteraction { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { }, updateHistoryFilter: { _ in + }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in }, chatController: { return nil diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index aac6940a512..88cc0fea697 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -401,12 +401,10 @@ public final class ChatPresentationInterfaceState: Equatable { } public struct HistoryFilter: Equatable { - public var customTags: [MemoryBuffer] - public var isActive: Bool + public var customTag: MemoryBuffer - public init(customTags: [MemoryBuffer], isActive: Bool) { - self.customTags = customTags - self.isActive = isActive + public init(customTag: MemoryBuffer) { + self.customTag = customTag } } @@ -441,6 +439,7 @@ public final class ChatPresentationInterfaceState: Equatable { public let search: ChatSearchData? public let searchQuerySuggestionResult: ChatPresentationInputQueryResult? public let historyFilter: HistoryFilter? + public let displayHistoryFilterAsList: Bool public let presentationReady: Bool public let chatWallpaper: TelegramWallpaper public let theme: PresentationTheme @@ -519,6 +518,7 @@ public final class ChatPresentationInterfaceState: Equatable { self.search = nil self.searchQuerySuggestionResult = nil self.historyFilter = nil + self.displayHistoryFilterAsList = false self.chatWallpaper = chatWallpaper self.presentationReady = false self.theme = theme @@ -566,7 +566,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?, 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) { + 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) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer @@ -598,6 +598,7 @@ public final class ChatPresentationInterfaceState: Equatable { self.search = search self.searchQuerySuggestionResult = searchQuerySuggestionResult self.historyFilter = historyFilter + self.displayHistoryFilterAsList = displayHistoryFilterAsList self.presentationReady = presentationReady self.chatWallpaper = chatWallpaper self.theme = theme @@ -751,6 +752,9 @@ public final class ChatPresentationInterfaceState: Equatable { if lhs.historyFilter != rhs.historyFilter { return false } + if lhs.displayHistoryFilterAsList != rhs.displayHistoryFilterAsList { + return false + } if lhs.presentationReady != rhs.presentationReady { return false } @@ -884,31 +888,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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } public func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { @@ -920,247 +924,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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) + } + + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } 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, 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) + 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) } } diff --git a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift index ea516e88a48..9427af0a0da 100644 --- a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift +++ b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift @@ -267,6 +267,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { var foreground: UInt32 var extractedBackground: UInt32 var extractedForeground: UInt32 + var extractedSelectedForeground: UInt32 var isSelected: Bool } @@ -381,7 +382,11 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { let foregroundColor: UIColor if isExtracted { backgroundColor = UIColor(argb: colors.extractedBackground) - foregroundColor = UIColor(argb: colors.extractedForeground) + if layout.colors.isSelected { + foregroundColor = UIColor(argb: colors.extractedSelectedForeground) + } else { + foregroundColor = UIColor(argb: colors.extractedForeground) + } } else { backgroundColor = UIColor(argb: colors.background) foregroundColor = UIColor(argb: colors.foreground) @@ -416,7 +421,12 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { let isForegroundTransparent = foregroundColor.alpha < 1.0 context.setBlendMode(isForegroundTransparent ? .copy : .normal) - let textOrigin: CGFloat = 36.0 + let textOrigin: CGFloat + if layout.isTag { + textOrigin = 32.0 + } else { + textOrigin = 36.0 + } var rightTextOrigin = textOrigin + totalComponentWidth @@ -642,26 +652,17 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { let boundingImageSize = CGSize(width: 20.0, height: 20.0) let imageSize: CGSize = boundingImageSize - /*if let file = spec.component.reaction.centerAnimation { - let defaultImageSize = CGSize(width: boundingImageSize.width + floor(boundingImageSize.width * 0.5 * 2.0), height: boundingImageSize.height + floor(boundingImageSize.height * 0.5 * 2.0)) - imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize - } else { - imageSize = boundingImageSize - }*/ var counterComponents: [String] = [] - for character in countString(Int64(spec.component.count)) { - counterComponents.append(String(character)) - } - - /*#if DEBUG - if spec.component.count % 2 == 0 { - counterComponents.removeAll() - for character in "123.5K" { + var hasTitle = false + if let title = spec.component.reaction.title, !title.isEmpty { + hasTitle = true + counterComponents.append(title) + } else { + for character in countString(Int64(spec.component.count)) { counterComponents.append(String(character)) } } - #endif*/ let backgroundColor = spec.component.chosenOrder != nil ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground @@ -672,7 +673,6 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { imageFrame = CGRect(origin: CGPoint(x: sideInsets + floorToScreenPixels((boundingImageSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize) } - var counterLayout: CounterLayout? var size = CGSize(width: boundingImageSize.width + sideInsets * 2.0, height: height) @@ -683,8 +683,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { } else { size.width -= 2.0 } - } else if spec.component.isTag { - size.width += 2.0 + } else if spec.component.isTag && !hasTitle { + size.width += 1.0 } else { let counterSpec = CounterLayout.Spec( stringComponents: counterComponents @@ -700,6 +700,9 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { } counterLayout = counterValue size.width += spacing + counterValue.size.width + if spec.component.isTag { + size.width += 5.0 + } } let backgroundColors = ReactionButtonAsyncNode.ContainerButtonNode.Colors( @@ -707,6 +710,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { foreground: spec.component.chosenOrder != nil ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground, extractedBackground: spec.component.colors.extractedBackground, extractedForeground: spec.component.colors.extractedForeground, + extractedSelectedForeground: spec.component.colors.extractedSelectedForeground, isSelected: spec.component.chosenOrder != nil ) var backgroundCounter: ReactionButtonAsyncNode.ContainerButtonNode.Counter? @@ -763,6 +767,10 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { override init(frame: CGRect) { self.containerView = ContextExtractedContentContainingView() + + self.containerView.isMultipleTouchEnabled = false + self.containerView.isExclusiveTouch = true + self.buttonNode = ContainerButtonNode() self.iconView = ReactionIconView() @@ -821,7 +829,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { guard let layout = self.layout else { return } - layout.spec.component.action(layout.spec.component.reaction.value) + layout.spec.component.action(self, layout.spec.component.reaction.value) } fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation, arguments: ReactionButtonsAsyncLayoutContainer.Arguments) { @@ -1002,11 +1010,13 @@ public final class ReactionButtonComponent: Equatable { public var value: MessageReaction.Reaction public var centerAnimation: TelegramMediaFile? public var animationFileId: Int64? + public var title: String? - public init(value: MessageReaction.Reaction, centerAnimation: TelegramMediaFile?, animationFileId: Int64?) { + public init(value: MessageReaction.Reaction, centerAnimation: TelegramMediaFile?, animationFileId: Int64?, title: String?) { self.value = value self.centerAnimation = centerAnimation self.animationFileId = animationFileId + self.title = title } public static func ==(lhs: Reaction, rhs: Reaction) -> Bool { @@ -1019,6 +1029,9 @@ public final class ReactionButtonComponent: Equatable { if lhs.animationFileId != rhs.animationFileId { return false } + if lhs.title != rhs.title { + return false + } return true } } @@ -1030,6 +1043,7 @@ public final class ReactionButtonComponent: Equatable { public var selectedForeground: UInt32 public var extractedBackground: UInt32 public var extractedForeground: UInt32 + public var extractedSelectedForeground: UInt32 public var deselectedMediaPlaceholder: UInt32 public var selectedMediaPlaceholder: UInt32 @@ -1040,6 +1054,7 @@ public final class ReactionButtonComponent: Equatable { selectedForeground: UInt32, extractedBackground: UInt32, extractedForeground: UInt32, + extractedSelectedForeground: UInt32, deselectedMediaPlaceholder: UInt32, selectedMediaPlaceholder: UInt32 ) { @@ -1049,6 +1064,7 @@ public final class ReactionButtonComponent: Equatable { self.selectedForeground = selectedForeground self.extractedBackground = extractedBackground self.extractedForeground = extractedForeground + self.extractedSelectedForeground = extractedSelectedForeground self.deselectedMediaPlaceholder = deselectedMediaPlaceholder self.selectedMediaPlaceholder = selectedMediaPlaceholder } @@ -1061,7 +1077,7 @@ public final class ReactionButtonComponent: Equatable { public let isTag: Bool public let count: Int public let chosenOrder: Int? - public let action: (MessageReaction.Reaction) -> Void + public let action: (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void public init( context: AccountContext, @@ -1071,7 +1087,7 @@ public final class ReactionButtonComponent: Equatable { isTag: Bool, count: Int, chosenOrder: Int?, - action: @escaping (MessageReaction.Reaction) -> Void + action: @escaping (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void ) { self.context = context self.colors = colors @@ -1218,7 +1234,7 @@ public final class ReactionButtonsAsyncLayoutContainer { public func update( context: AccountContext, - action: @escaping (MessageReaction.Reaction) -> Void, + action: @escaping (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void, reactions: [ReactionButtonsAsyncLayoutContainer.Reaction], colors: ReactionButtonComponent.Colors, isTag: Bool, diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 91a047afe8c..76b26413475 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -2255,6 +2255,7 @@ public final class ContextController: ViewController, StandalonePresentableContr public var reactionItems: [ReactionContextItem] public var selectedReactionItems: Set public var reactionsTitle: String? + public var reactionsLocked: Bool public var animationCache: AnimationCache? public var alwaysAllowPremiumReactions: Bool public var allPresetReactionsAreAvailable: Bool @@ -2271,6 +2272,7 @@ public final class ContextController: ViewController, StandalonePresentableContr reactionItems: [ReactionContextItem] = [], selectedReactionItems: Set = Set(), reactionsTitle: String? = nil, + reactionsLocked: Bool = false, animationCache: AnimationCache? = nil, alwaysAllowPremiumReactions: Bool = false, allPresetReactionsAreAvailable: Bool = false, @@ -2287,6 +2289,7 @@ public final class ContextController: ViewController, StandalonePresentableContr self.reactionItems = reactionItems self.selectedReactionItems = selectedReactionItems self.reactionsTitle = reactionsTitle + self.reactionsLocked = reactionsLocked self.alwaysAllowPremiumReactions = alwaysAllowPremiumReactions self.allPresetReactionsAreAvailable = allPresetReactionsAreAvailable self.getEmojiContent = getEmojiContent @@ -2303,6 +2306,7 @@ public final class ContextController: ViewController, StandalonePresentableContr self.reactionItems = [] self.selectedReactionItems = Set() self.reactionsTitle = nil + self.reactionsLocked = false self.alwaysAllowPremiumReactions = false self.allPresetReactionsAreAvailable = false self.getEmojiContent = nil diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 4695e614d17..5eade7cd822 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -40,16 +40,18 @@ public struct ContextControllerReactionItems { public var reactionItems: [ReactionContextItem] public var selectedReactionItems: Set public var reactionsTitle: String? + public var reactionsLocked: Bool public var animationCache: AnimationCache public var alwaysAllowPremiumReactions: Bool public var allPresetReactionsAreAvailable: Bool public var getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)? - public init(context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?) { + public init(context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, reactionsTitle: String?, reactionsLocked: Bool, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?) { self.context = context self.reactionItems = reactionItems self.selectedReactionItems = selectedReactionItems self.reactionsTitle = reactionsTitle + self.reactionsLocked = reactionsLocked self.animationCache = animationCache self.alwaysAllowPremiumReactions = alwaysAllowPremiumReactions self.allPresetReactionsAreAvailable = allPresetReactionsAreAvailable @@ -1074,6 +1076,7 @@ func makeContextControllerActionsStackItem(items: ContextController.Items) -> [C reactionItems: items.reactionItems, selectedReactionItems: items.selectedReactionItems, reactionsTitle: items.reactionsTitle, + reactionsLocked: items.reactionsLocked, animationCache: animationCache, alwaysAllowPremiumReactions: items.alwaysAllowPremiumReactions, allPresetReactionsAreAvailable: items.allPresetReactionsAreAvailable, diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index dbc61b83525..f6949c03bd0 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -637,8 +637,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo var animateReactionsIn = false var contentTopInset: CGFloat = topInset var removedReactionContextNode: ReactionContextNode? - // MARK: Nicegram - if let reactionItems = self.actionsStackNode.topReactionItems, !reactionItems.reactionItems.isEmpty, !UserDefaults.standard.bool(forKey: "hideReactions") { + if let reactionItems = self.actionsStackNode.topReactionItems, !reactionItems.reactionItems.isEmpty { let reactionContextNode: ReactionContextNode if let current = self.reactionContextNode { reactionContextNode = current @@ -650,6 +649,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo items: reactionItems.reactionItems, selectedItems: reactionItems.selectedReactionItems, title: reactionItems.reactionsTitle, + reactionsLocked: reactionItems.reactionsLocked, alwaysAllowPremiumReactions: reactionItems.alwaysAllowPremiumReactions, allPresetReactionsAreAvailable: reactionItems.allPresetReactionsAreAvailable, getEmojiContent: reactionItems.getEmojiContent, @@ -692,6 +692,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo return } + if let reactionItems = strongSelf.actionsStackNode.topReactionItems, !reactionItems.reactionItems.isEmpty { + if reactionItems.allPresetReactionsAreAvailable { + controller.premiumReactionsSelected?() + return + } + } + if let file = file, let reactionContextNode = strongSelf.reactionContextNode { let position: UndoOverlayController.Position let insets = validLayout.insets(options: .statusBar) @@ -717,6 +724,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo controller.premiumReactionsSelected?() } } + + reactionContextNode.updateLayout(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right), anchorRect: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: 1.0, height: 1.0)), isCoveredByInput: false, isAnimatingOut: false, transition: .immediate) } contentTopInset += reactionContextNode.contentHeight + 18.0 } else if let reactionContextNode = self.reactionContextNode { @@ -744,7 +753,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo case let .location(location): if let transitionInfo = location.transitionInfo() { contentRect = CGRect(origin: transitionInfo.location, size: CGSize(width: 1.0, height: 1.0)) - contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minX), size: CGSize(width: layout.size.width, height: contentRect.height)) + contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minY), size: CGSize(width: layout.size.width, height: contentRect.height)) } else { return } @@ -752,7 +761,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let transitionInfo = reference.transitionInfo() { contentRect = convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0) contentRect.size.width += 5.0 - contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minX), size: CGSize(width: layout.size.width, height: contentRect.height)) + contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minY), size: CGSize(width: layout.size.width, height: contentRect.height)) } else { return } @@ -1599,6 +1608,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if self.reactionContextNodeIsAnimatingOut, let reactionContextNode = self.reactionContextNode { reactionContextNode.bounds = reactionContextNode.bounds.offsetBy(dx: 0.0, dy: offset.y) transition.animateOffsetAdditive(node: reactionContextNode, offset: -offset.y) + + if let itemContentNode = self.itemContentNode { + itemContentNode.bounds = itemContentNode.bounds.offsetBy(dx: 0.0, dy: offset.y) + transition.animateOffsetAdditive(node: itemContentNode, offset: -offset.y) + } } } } diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index b07983797aa..295389de4a6 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -83,8 +83,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { case keepChatNavigationStack(PresentationTheme, Bool) case skipReadHistory(PresentationTheme, Bool) case dustEffect(Bool) - case callV2(Bool) - case alternativeStoryMedia(Bool) case crashOnSlowQueries(PresentationTheme, Bool) case crashOnMemoryPressure(PresentationTheme, Bool) case clearTips(PresentationTheme) @@ -142,7 +140,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .webViewInspection, .resetWebViewCache: return DebugControllerSection.web.rawValue - case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .callV2, .alternativeStoryMedia, .crashOnSlowQueries, .crashOnMemoryPressure: + case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure: return DebugControllerSection.experiments.rawValue case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases: return DebugControllerSection.experiments.rawValue @@ -201,10 +199,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 16 case .dustEffect: return 17 - case .callV2: - return 18 - case .alternativeStoryMedia: - return 19 case .crashOnSlowQueries: return 20 case .crashOnMemoryPressure: @@ -1009,22 +1003,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { return settings }).start() }) - case let .callV2(value): - return ItemListSwitchItem(presentationData: presentationData, title: "CallV2", value: value, sectionId: self.section, style: .blocks, updated: { value in - let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in - var settings = settings - settings.callV2 = value - return settings - }).start() - }) - case let .alternativeStoryMedia(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Story Data Saver", value: value, sectionId: self.section, style: .blocks, updated: { value in - let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in - var settings = settings - settings.alternativeStoryMedia = value - return settings - }).start() - }) case let .crashOnSlowQueries(_, value): return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in @@ -1513,8 +1491,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory)) #endif entries.append(.dustEffect(experimentalSettings.dustEffect)) - entries.append(.callV2(experimentalSettings.callV2)) - entries.append(.alternativeStoryMedia(experimentalSettings.alternativeStoryMedia)) } entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries)) entries.append(.crashOnMemoryPressure(presentationData.theme, experimentalSettings.crashOnMemoryPressure)) diff --git a/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift b/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift index 11e2107c4e7..7d6291b9aa4 100644 --- a/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift +++ b/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift @@ -133,8 +133,12 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode { text = strings.ChatList_DeleteChatConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder)) } case .deleteSavedPeer: - let peerTitle = peer.displayTitle(strings: strings, displayOrder: nameOrder) - text = strings.ChatList_DeleteSavedPeerConfirmation(peerTitle) + if peer.id == context.account.peerId { + text = strings.ChatList_DeleteSavedPeerMyNotesConfirmation(strings.ChatList_DeleteSavedPeerMyNotesConfirmationTitle) + } else { + let peerTitle = peer.displayTitle(strings: strings, displayOrder: nameOrder) + text = strings.ChatList_DeleteSavedPeerConfirmation(peerTitle) + } case let .clearHistory(canClearCache): if peer.id == context.account.peerId { text = PresentationStrings.FormattedString(string: strings.ChatList_ClearSavedMessagesConfirmation, ranges: []) diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index a5b8eefe162..86fd7ffcc1a 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -994,6 +994,37 @@ public extension ContainedViewLayoutTransition { } } + func updateTintColor(view: UIView, color: UIColor, completion: ((Bool) -> Void)? = nil) { + if let current = view.layer.layerTintColor, UIColor(cgColor: current) == color { + completion?(true) + return + } + + switch self { + case .immediate: + view.tintColor = color + view.layer.layerTintColor = color.cgColor + completion?(true) + case let .animated(duration, curve): + let previousColor: CGColor = view.layer.layerTintColor ?? UIColor.clear.cgColor + view.tintColor = color + view.layer.layerTintColor = color.cgColor + + view.layer.animate( + from: previousColor, + to: color.cgColor, + keyPath: "contentsMultiplyColor", + timingFunction: curve.timingFunction, + duration: duration, + delay: 0.0, + mediaTimingFunction: curve.mediaTimingFunction, + removeOnCompletion: true, + additive: false, + completion: completion + ) + } + } + func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)? = nil) { if layer.contentsRect == contentsRect { if let completion = completion { diff --git a/submodules/Display/Source/ContextControllerSourceNode.swift b/submodules/Display/Source/ContextControllerSourceNode.swift index 4dc6d55fdcb..2de0835f389 100644 --- a/submodules/Display/Source/ContextControllerSourceNode.swift +++ b/submodules/Display/Source/ContextControllerSourceNode.swift @@ -181,6 +181,9 @@ open class ContextControllerSourceView: UIView { override public init(frame: CGRect) { super.init(frame: frame) + self.isMultipleTouchEnabled = false + self.isExclusiveTouch = true + let contextGesture = ContextGesture(target: self, action: nil) self.contextGesture = contextGesture self.addGestureRecognizer(contextGesture) diff --git a/submodules/Display/Source/HapticFeedback.swift b/submodules/Display/Source/HapticFeedback.swift index a0a714adaff..63b07ac513a 100644 --- a/submodules/Display/Source/HapticFeedback.swift +++ b/submodules/Display/Source/HapticFeedback.swift @@ -34,32 +34,11 @@ private final class HapticFeedbackImpl { }() private lazy var selectionGenerator: UISelectionFeedbackGenerator? = { - let generator = UISelectionFeedbackGenerator() - generator.prepare() - var string = generator.debugDescription - string.removeLast() - let number = string.suffix(1) - if number == "1" { - return generator - } else { - if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { - return generator - } - return nil - } + return UISelectionFeedbackGenerator() }() private lazy var notificationGenerator: UINotificationFeedbackGenerator? = { - let generator = UINotificationFeedbackGenerator() - generator.prepare() - var string = generator.debugDescription - string.removeLast() - let number = string.suffix(1) - if number == "1" { - return generator - } else { - return nil - } + return UINotificationFeedbackGenerator() }() func prepareTap() { diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index ce2f60b0d02..515e7966d16 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -1827,6 +1827,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil, stackFromBottom: self.stackFromBottom) } + public func addAfterTransactionsCompleted(_ f: @escaping () -> Void) { + self.transactionQueue.addTransaction({ transactionCompletion in + f() + transactionCompletion() + }) + } + public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil && additionalScrollDistance.isZero { if let updateOpaqueState = updateOpaqueState { @@ -2147,7 +2154,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let beginReplay = { [weak self] in if let strongSelf = self { - strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateCrossfade: options.contains(.AnimateCrossfade), synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: { + strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateCrossfade: options.contains(.AnimateCrossfade), animateFullTransition: options.contains(.AnimateFullTransition), synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: { if options.contains(.PreferSynchronousDrawing) { self?.recursivelyEnsureDisplaySynchronously(true) } @@ -2370,7 +2377,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, (ListViewItemApply) -> Void), timestamp: Double, listInsets: UIEdgeInsets, visibleBounds: CGRect) { + private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, animateFullTransition: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, (ListViewItemApply) -> Void), timestamp: Double, listInsets: UIEdgeInsets, visibleBounds: CGRect) { let insertionOrigin = self.referencePointForInsertionAtIndex(nodeIndex) let nodeOrigin: CGPoint @@ -2513,11 +2520,16 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), short: false) } } - } else if animateAlpha && previousFrame == nil { - if forceAnimateInsertion { - node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), short: true) - } else { - node.animateAdded(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) + } else if animateAlpha { + if previousFrame == nil { + if forceAnimateInsertion { + node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), short: true) + } else if animateFullTransition { + node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + node.layer.animateScale(from: 0.7, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } else { + node.animateAdded(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) + } } } @@ -2605,7 +2617,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, synchronous: Bool, synchronousLoads: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { + private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateFullTransition: Bool, synchronous: Bool, synchronousLoads: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { var scrollToItem: ListViewScrollToItem? var isExperimentalSnapToScrollToItem = false if let originalScrollToItem = originalScrollToItem { @@ -2640,6 +2652,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if let updateSizeAndInsets = updateSizeAndInsets { if updateSizeAndInsets.size != self.visibleSize || updateSizeAndInsets.insets != self.insets { sizeOrInsetsUpdated = true + if updateSizeAndInsets.insets.top == 83.0 && updateSizeAndInsets.duration < 0.5 { + assert(true) + } } } @@ -2658,9 +2673,42 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture previousTopItemVerticalOrigin = self.topItemVerticalOrigin() } - var previousApparentFrames: [(ListViewItemNode, CGRect)] = [] + struct PreviousApparentFrame { + var frame: CGRect + var insets: UIEdgeInsets + + init(frame: CGRect, insets: UIEdgeInsets) { + self.frame = frame + self.insets = insets + } + } + + var previousApparentFrames: [(ListViewItemNode, PreviousApparentFrame)] = [] for itemNode in self.itemNodes { - previousApparentFrames.append((itemNode, itemNode.apparentFrame)) + previousApparentFrames.append((itemNode, PreviousApparentFrame( + frame: itemNode.apparentFrame, + insets: itemNode.insets + ))) + } + + struct PreviousHeaderNodeFrame { + var frame: CGRect + var alpha: CGFloat + + init(frame: CGRect, alpha: CGFloat) { + self.frame = frame + self.alpha = alpha + } + } + + var previousHeaderNodeFrames: [(ListViewItemHeaderNode, PreviousHeaderNodeFrame)] = [] + if animateFullTransition { + for (_, itemHeaderNode) in self.itemHeaderNodes { + previousHeaderNodeFrames.append((itemHeaderNode, PreviousHeaderNodeFrame( + frame: itemHeaderNode.frame, + alpha: itemHeaderNode.getEffectiveAlpha() + ))) + } } var takenPreviousNodes = Set() @@ -2669,6 +2717,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture takenPreviousNodes.insert(node.syncWith({ $0 })) } } + var removedPreviousNodes = Set() let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow() var hadInserts = false @@ -2683,7 +2732,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var previousFrame: CGRect? for (previousNode, frame) in previousApparentFrames { if previousNode === node { - previousFrame = frame + previousFrame = frame.frame break } } @@ -2696,7 +2745,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture updatedPreviousFrame = nil } - self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) + self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, animateFullTransition: animateFullTransition, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) hadInserts = true hadChangesToItemNodes = true if let _ = updatedPreviousFrame { @@ -2748,7 +2797,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture for (node, previousFrame) in previousApparentFrames { if node === referenceNode { - height = previousFrame.size.height + height = previousFrame.frame.size.height previousLayout = ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets) break } @@ -2757,10 +2806,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if let height = height, let previousLayout = previousLayout { if takenPreviousNodes.contains(referenceNode) { let tempNode = ListViewTempItemNode(layerBacked: true) - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: tempNode, layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, { _ in }) }, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) + self.insertNodeAtIndex(animated: false, animateAlpha: false, animateFullTransition: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: tempNode, layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, { _ in }) }, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) } else { referenceNode.index = nil - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, { _ in }) }, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) + self.insertNodeAtIndex(animated: false, animateAlpha: false, animateFullTransition: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, { _ in }) }, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) if let verticalScrollIndicator = self.verticalScrollIndicator { self.insertSubnode(referenceNode, belowSubnode: verticalScrollIndicator) } else { @@ -2809,8 +2858,18 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } } - - self.removeItemNodeAtIndex(index) + + if animateFullTransition { + for (previousNode, previousFrame) in previousApparentFrames { + if previousNode === self.itemNodes[index] { + removedPreviousNodes.insert(previousNode) + self.itemNodes[index].frame = previousFrame.frame + break + } + } + } + + self.removeItemNodeAtIndex(index, animateFullTransition: animateFullTransition) hadChangesToItemNodes = true case let .UpdateLayout(index, layout, apply): let node = self.itemNodes[index] @@ -3000,7 +3059,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if let index = itemNode.index , index == stationaryItemIndex { for (previousNode, previousFrame) in previousApparentFrames { if previousNode === itemNode { - let offset = previousFrame.minY - itemNode.frame.minY + let offset = previousFrame.frame.minY - itemNode.frame.minY if abs(offset) > CGFloat.ulpOfOne { for itemNode in self.itemNodes { @@ -3265,6 +3324,52 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.highlightedItemIndex = nil } + if animateFullTransition { + for (previousNode, previousFrame) in previousApparentFrames { + if !takenPreviousNodes.contains(previousNode) && !removedPreviousNodes.contains(previousNode) { + if previousFrame.frame.maxY < self.insets.top || previousFrame.frame.minY > self.visibleSize.height - self.insets.bottom { + previousNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + previousNode.layer.animateScale(from: 0.7, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } else { + let boundsOffset: CGFloat + if self.rotated { + boundsOffset = previousFrame.insets.bottom - previousNode.insets.bottom + } else { + boundsOffset = previousFrame.insets.top - previousNode.insets.top + } + previousNode.layer.animatePosition(from: CGPoint(x: 0.0, y: previousFrame.frame.minY - previousNode.frame.minY + boundsOffset), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + } + } + } + + let applyHeaderNodesFullTransition: () -> Void = { + if animateFullTransition { + for (_, itemHeaderNode) in self.itemHeaderNodes { + var found = false + inner: for (previousHeaderNode, previousFrame) in previousHeaderNodeFrames { + if itemHeaderNode === previousHeaderNode && previousHeaderNode.supernode === self { + found = true + + if previousFrame.frame.maxY < self.insets.top || previousFrame.frame.minY > self.visibleSize.height - self.insets.bottom || previousFrame.alpha == 0.0 { + itemHeaderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + itemHeaderNode.layer.animateScale(from: 0.7, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } else { + itemHeaderNode.layer.animatePosition(from: CGPoint(x: 0.0, y: previousFrame.frame.minY - itemHeaderNode.frame.minY), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + + break inner + } + } + + if !found { + itemHeaderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + itemHeaderNode.layer.animateScale(from: 0.7, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } + } + } + } + if let scrollToItem = scrollToItem, scrollToItem.animated { if self.itemNodes.count != 0 { var offset: CGFloat? @@ -3276,16 +3381,16 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture for (previousNode, previousFrame) in previousApparentFrames { if previousNode.supernode == nil { temporaryPreviousNodes.append(previousNode) - previousNode.updateFrame(previousFrame, within: self.visibleSize) - if previousUpperBound == nil || previousUpperBound! > previousFrame.minY { - previousUpperBound = previousFrame.minY + previousNode.updateFrame(previousFrame.frame, within: self.visibleSize) + if previousUpperBound == nil || previousUpperBound! > previousFrame.frame.minY { + previousUpperBound = previousFrame.frame.minY } - if previousLowerBound == nil || previousLowerBound! < previousFrame.maxY { - previousLowerBound = previousFrame.maxY + if previousLowerBound == nil || previousLowerBound! < previousFrame.frame.maxY { + previousLowerBound = previousFrame.frame.maxY } } else { if previousNode.canBeUsedAsScrollToItemAnchor { - offset = previousNode.apparentFrame.minY - previousFrame.minY + offset = previousNode.apparentFrame.minY - previousFrame.frame.minY break } } @@ -3294,16 +3399,16 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture for (previousNode, previousFrame) in previousApparentFrames { if previousNode.supernode == nil { temporaryPreviousNodes.append(previousNode) - previousNode.updateFrame(previousFrame, within: self.visibleSize) - if previousUpperBound == nil || previousUpperBound! > previousFrame.minY { - previousUpperBound = previousFrame.minY + previousNode.updateFrame(previousFrame.frame, within: self.visibleSize) + if previousUpperBound == nil || previousUpperBound! > previousFrame.frame.minY { + previousUpperBound = previousFrame.frame.minY } - if previousLowerBound == nil || previousLowerBound! < previousFrame.maxY { - previousLowerBound = previousFrame.maxY + if previousLowerBound == nil || previousLowerBound! < previousFrame.frame.maxY { + previousLowerBound = previousFrame.frame.maxY } } else { if previousNode.canBeUsedAsScrollToItemAnchor { - offset = previousNode.apparentFrame.minY - previousFrame.minY + offset = previousNode.apparentFrame.minY - previousFrame.frame.minY } } } @@ -3341,7 +3446,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture previousItemHeaderNodes.append(headerNode) } - self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, synchronousLoad: synchronousLoads, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, synchronousLoad: synchronousLoads, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty, animateFullTransition: animateFullTransition) if let offset = offset, !offset.isZero { //self.didScrollWithOffset?(-offset, headerNodesTransition.0, nil) @@ -3551,6 +3656,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) } + applyHeaderNodesFullTransition() + self.setNeedsAnimations() self.updateVisibleContentOffset() @@ -3562,9 +3669,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture completion() } else { - self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, synchronousLoad: synchronousLoads, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, synchronousLoad: synchronousLoads, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty, animateFullTransition: animateFullTransition) self.updateItemNodesVisibilities(onlyPositive: deferredUpdateVisible) + applyHeaderNodesFullTransition() + if animated { self.setNeedsAnimations() } @@ -3605,16 +3714,33 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - private func removeItemNodeAtIndex(_ index: Int) { + private func removeItemNodeAtIndex(_ index: Int, animateFullTransition: Bool) { let node = self.itemNodes[index] self.itemNodes.remove(at: index) - node.visibility = .none - node.removeFromSupernode() - node.extractedBackgroundNode?.removeFromSupernode() - node.accessoryItemNode?.removeFromSupernode() - node.setAccessoryItemNode(nil, leftInset: self.insets.left, rightInset: self.insets.right) - node.headerAccessoryItemNode?.removeFromSupernode() - node.headerAccessoryItemNode = nil + + if animateFullTransition { + node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak node] _ in + guard let node else { + return + } + node.visibility = .none + node.removeFromSupernode() + node.extractedBackgroundNode?.removeFromSupernode() + node.accessoryItemNode?.removeFromSupernode() + node.setAccessoryItemNode(nil, leftInset: self.insets.left, rightInset: self.insets.right) + node.headerAccessoryItemNode?.removeFromSupernode() + node.headerAccessoryItemNode = nil + }) + node.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + } else { + node.visibility = .none + node.removeFromSupernode() + node.extractedBackgroundNode?.removeFromSupernode() + node.accessoryItemNode?.removeFromSupernode() + node.setAccessoryItemNode(nil, leftInset: self.insets.left, rightInset: self.insets.right) + node.headerAccessoryItemNode?.removeFromSupernode() + node.headerAccessoryItemNode = nil + } } private var nextHeaderSpaceAffinity: Int = 0 @@ -3688,7 +3814,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - private func updateItemHeaders(leftInset: CGFloat, rightInset: CGFloat, synchronousLoad: Bool, transition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0), animateInsertion: Bool = false) { + private func updateItemHeaders(leftInset: CGFloat, rightInset: CGFloat, synchronousLoad: Bool, transition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0), animateInsertion: Bool = false, animateFullTransition: Bool = false) { self.assignHeaderSpaceAffinities() let upperDisplayBound = self.headerInsets.top @@ -3895,7 +4021,17 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let currentIds = Set(self.itemHeaderNodes.keys) for id in currentIds.subtracting(Set(visibleHeaderNodes)) { if let headerNode = self.itemHeaderNodes.removeValue(forKey: id) { - headerNode.removeFromSupernode() + if animateFullTransition { + headerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak headerNode] _ in + guard let headerNode else { + return + } + headerNode.removeFromSupernode() + }) + headerNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + } else { + headerNode.removeFromSupernode() + } } } } @@ -4219,7 +4355,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture while i < self.itemNodes.count { let node = self.itemNodes[i] if node.index == nil && node.apparentHeight <= CGFloat.ulpOfOne { - self.removeItemNodeAtIndex(i) + self.removeItemNodeAtIndex(i, animateFullTransition: false) } else { i += 1 } @@ -4233,10 +4369,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) if synchronous { - self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, synchronous: false, synchronousLoads: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) + self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateFullTransition: false, synchronous: false, synchronousLoads: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) } else { self.dispatchOnVSync { - self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, synchronous: false, synchronousLoads: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) + self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateFullTransition: false, synchronous: false, synchronousLoads: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) } } } diff --git a/submodules/Display/Source/ListViewIntermediateState.swift b/submodules/Display/Source/ListViewIntermediateState.swift index 70680497eba..89da117cd23 100644 --- a/submodules/Display/Source/ListViewIntermediateState.swift +++ b/submodules/Display/Source/ListViewIntermediateState.swift @@ -129,6 +129,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128) public static let AnimateCrossfade = ListViewDeleteAndInsertOptions(rawValue: 256) public static let ForceUpdate = ListViewDeleteAndInsertOptions(rawValue: 512) + public static let AnimateFullTransition = ListViewDeleteAndInsertOptions(rawValue: 1024) } public struct ListViewUpdateSizeAndInsets { diff --git a/submodules/Display/Source/ListViewItemHeader.swift b/submodules/Display/Source/ListViewItemHeader.swift index a2551cdb0d5..8842efc8a2e 100644 --- a/submodules/Display/Source/ListViewItemHeader.swift +++ b/submodules/Display/Source/ListViewItemHeader.swift @@ -45,6 +45,10 @@ open class ListViewItemHeaderNode: ASDisplayNode { open func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { } + open func getEffectiveAlpha() -> CGFloat { + return self.alpha + } + public init(layerBacked: Bool = false, dynamicBounce: Bool = false, isRotated: Bool = false, seeThrough: Bool = false) { self.wantsScrollDynamics = dynamicBounce self.isRotated = isRotated diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 1ddb6767cf8..a6d3392043f 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -493,6 +493,8 @@ open class NavigationBar: ASDisplayNode { public var makeCustomTransitionNode: ((NavigationBar, Bool) -> CustomNavigationTransitionNode?)? public var allowsCustomTransition: (() -> Bool)? + public var customSetContentNode: ((NavigationBarContentNode?, Bool) -> Void)? + private var collapsed: Bool { get { return self.frame.size.height.isLess(than: 44.0) @@ -1649,6 +1651,11 @@ open class NavigationBar: ASDisplayNode { } public func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) { + if let customSetContentNode = self.customSetContentNode { + customSetContentNode(contentNode, animated) + return + } + if self.contentNode !== contentNode { if let previous = self.contentNode { if animated { diff --git a/submodules/Display/Source/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift index fe281a62114..ea05895c763 100644 --- a/submodules/Display/Source/NavigationButtonNode.swift +++ b/submodules/Display/Source/NavigationButtonNode.swift @@ -500,7 +500,7 @@ public final class NavigationButtonNode: ContextControllerSourceNode { var totalHeight: CGFloat = 0.0 for i in 0 ..< self.nodes.count { if i != 0 { - nodeOrigin.x += 10.0 + nodeOrigin.x += 15.0 } let node = self.nodes[i] diff --git a/submodules/DrawingUI/Sources/DrawingReactionView.swift b/submodules/DrawingUI/Sources/DrawingReactionView.swift index 70fc7eeef45..7a3b597a527 100644 --- a/submodules/DrawingUI/Sources/DrawingReactionView.swift +++ b/submodules/DrawingUI/Sources/DrawingReactionView.swift @@ -136,6 +136,7 @@ public class DrawingReactionEntityView: DrawingStickerEntityView { items: reactionItems.map(ReactionContextItem.reaction), selectedItems: Set(), title: nil, + reactionsLocked: false, alwaysAllowPremiumReactions: false, allPresetReactionsAreAvailable: false, getEmojiContent: { [weak self] animationCache, animationRenderer in diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 6dec02a52b3..0fa851fea05 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -91,7 +91,7 @@ public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galle return result } -public func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { +public func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatFilterTag: MemoryBuffer?, chatLocationContextHolder: Atomic?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { var standalone = standalone if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.namespace != Namespaces.Message.Cloud { standalone = true @@ -293,8 +293,8 @@ public enum ChatMessagePreviewControllerData { case gallery(GalleryController) } -public func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? { - if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { +public func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatFilterTag: MemoryBuffer?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? { + if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { switch mediaData { case .gallery: break @@ -307,8 +307,8 @@ public func chatMessagePreviewControllerData(context: AccountContext, chatLocati return nil } -public func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal { - if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { +public func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatFilterTag: MemoryBuffer?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal { + if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { switch mediaData { case let .gallery(gallery): return gallery diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 6000609303e..1ad9b62ab5e 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -1418,7 +1418,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } private func commitDeleteMessages(_ messages: [EngineMessage], ask: Bool) { - self.messageContextDisposable.set((self.context.sharedContext.chatAvailableMessageActions(engine: self.context.engine, accountPeerId: self.context.account.peerId, messageIds: Set(messages.map { $0.id })) |> deliverOnMainQueue).start(next: { [weak self] actions in + self.messageContextDisposable.set((self.context.sharedContext.chatAvailableMessageActions(engine: self.context.engine, accountPeerId: self.context.account.peerId, messageIds: Set(messages.map { $0.id }), keepUpdated: false) |> deliverOnMainQueue).start(next: { [weak self] actions in if let strongSelf = self, let controllerInteration = strongSelf.controllerInteraction, !actions.options.isEmpty { var presentationData = strongSelf.presentationData if !presentationData.theme.overallDarkAppearance { diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index eb2269ee5a1..407c45656f1 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -194,7 +194,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { guard let navigationController = self.getNavigationController() else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false diff --git a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift index 9cf0cf48275..4cc28c8d60a 100644 --- a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift @@ -396,7 +396,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese guard let navigationController = navigationController?() else { return } - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift index 9b784ac5ae5..0f3c3b8d080 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift @@ -476,7 +476,7 @@ public final class InviteLinkInviteController: ViewController { guard let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index 5207eff92b8..c37972d0afc 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -474,7 +474,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio guard let navigationController = navigationController?() else { return } - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false @@ -690,7 +690,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio guard let navigationController = navigationController?() else { return } - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index d6271c8fc62..dcbe0ca9dcb 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -546,7 +546,7 @@ public final class InviteLinkViewController: ViewController { guard let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false diff --git a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift index 5dcf593bd1c..c0e84873b12 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift @@ -47,6 +47,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem { let title: String let titleColor: ItemListDisclosureItemTitleColor let titleFont: ItemListDisclosureItemTitleFont + let titleIcon: UIImage? let enabled: Bool let label: String let labelStyle: ItemListDisclosureLabelStyle @@ -59,7 +60,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem { public let tag: ItemListItemTag? public let shimmeringIndex: Int? - public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { + public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { self.presentationData = presentationData self.icon = icon self.context = context @@ -67,6 +68,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem { self.title = title self.titleColor = titleColor self.titleFont = titleFont + self.titleIcon = titleIcon self.enabled = enabled self.labelStyle = labelStyle self.label = label @@ -138,6 +140,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { var avatarNode: AvatarNode? let iconNode: ASImageNode let titleNode: TextNode + let titleIconNode: ASImageNode public let labelNode: TextNode var additionalDetailLabelNode: TextNode? let arrowNode: ASImageNode @@ -184,6 +187,10 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false + self.titleIconNode = ASImageNode() + self.titleIconNode.displayWithoutProcessing = true + self.titleIconNode.displaysAsynchronously = false + self.labelNode = TextNode() self.labelNode.isUserInteractionEnabled = false @@ -626,6 +633,19 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { strongSelf.additionalDetailLabelNode = nil additionalDetailLabelNode.removeFromSupernode() } + + if let titleIcon = item.titleIcon { + if strongSelf.titleIconNode.supernode == nil { + strongSelf.addSubnode(strongSelf.titleIconNode) + } + + strongSelf.titleIconNode.image = titleIcon + strongSelf.titleIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + 5.0, y: floor((layout.contentSize.height - titleIcon.size.height) / 2.0) - 1.0), size: titleIcon.size) + } else { + if strongSelf.titleIconNode.supernode != nil { + strongSelf.titleIconNode.removeFromSupernode() + } + } if case .textWithIcon = item.labelStyle { if let updatedLabelImage = updatedLabelImage { diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 3a37963f16f..aa2b834f6ee 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -566,6 +566,9 @@ final class MutableChatListView { private var currentHiddenPeerIds = Set() + private let displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey + private(set) var displaySavedMessagesAsTopicList: PreferencesEntry? + init(postbox: PostboxImpl, currentTransaction: Transaction, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) { self.groupId = groupId self.filterPredicate = filterPredicate @@ -574,6 +577,8 @@ final class MutableChatListView { self.currentHiddenPeerIds = postbox.hiddenChatIds + self.displaySavedMessagesAsTopicListPreferencesKey = postbox.seedConfiguration.displaySavedMessagesAsTopicListPreferencesKey + var spaces: [ChatListViewSpace] = [ .group(groupId: self.groupId, pinned: .notPinned, predicate: filterPredicate) ] @@ -612,6 +617,8 @@ final class MutableChatListView { } else { self.groupEntries = [] } + + self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey) } private func reloadGroups(postbox: PostboxImpl) { @@ -689,6 +696,8 @@ final class MutableChatListView { } } } + + self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey) } func refreshDueToExternalTransaction(postbox: PostboxImpl, currentTransaction: Transaction) -> Bool { @@ -699,12 +708,16 @@ final class MutableChatListView { updated = true let currentGroupEntries = self.groupEntries + let currentDisplaySavedMessagesAsTopicList = self.displaySavedMessagesAsTopicList self.reloadGroups(postbox: postbox) if self.groupEntries != currentGroupEntries { updated = true } + if self.displaySavedMessagesAsTopicList != currentDisplaySavedMessagesAsTopicList { + updated = true + } return updated } @@ -734,6 +747,20 @@ final class MutableChatListView { } } + if !transaction.currentPreferencesOperations.isEmpty { + for operation in transaction.currentPreferencesOperations { + switch operation { + case let .update(key, value): + if key == self.displaySavedMessagesAsTopicListPreferencesKey { + if self.displaySavedMessagesAsTopicList != value { + self.displaySavedMessagesAsTopicList = value + hasChanges = true + } + } + } + } + } + if case .root = self.groupId, self.filterPredicate == nil { var invalidatedGroups = false for (groupId, groupOperations) in operations { @@ -938,6 +965,7 @@ public final class ChatListView { public let groupEntries: [ChatListGroupReferenceEntry] public let earlierIndex: ChatListIndex? public let laterIndex: ChatListIndex? + public let displaySavedMessagesAsTopicList: PreferencesEntry? init(_ mutableView: MutableChatListView) { self.groupId = mutableView.groupId @@ -1006,5 +1034,6 @@ public final class ChatListView { } self.additionalItemEntries = additionalItemEntries + self.displaySavedMessagesAsTopicList = mutableView.displaySavedMessagesAsTopicList } } diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 52c13bfdc14..e5941a6e483 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -301,6 +301,12 @@ final class MessageHistoryTable: Table { } self.summaryTable.addMessage(key: MessageHistoryTagsSummaryKey(tag: MessageTags(), peerId: message.id.peerId, threadId: nil, namespace: message.id.namespace, customTag: customTag), id: message.id.id, isNewlyAdded: true, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + + if let threadId = message.threadId { + self.threadsTable.add(threadId: threadId, index: message.index) + + self.summaryTable.addMessage(key: MessageHistoryTagsSummaryKey(tag: MessageTags(), peerId: message.id.peerId, threadId: threadId, namespace: message.id.namespace, customTag: customTag), id: message.id.id, isNewlyAdded: true, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + } } let globalTags = message.globalTags.rawValue @@ -1386,6 +1392,10 @@ final class MessageHistoryTable: Table { } self.summaryTable.removeMessage(key: MessageHistoryTagsSummaryKey(tag: MessageTags(), peerId: message.id.peerId, threadId: nil, namespace: message.id.namespace, customTag: customTag), id: message.id.id, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + + if let threadId = message.threadId { + self.summaryTable.removeMessage(key: MessageHistoryTagsSummaryKey(tag: MessageTags(), peerId: message.id.peerId, threadId: threadId, namespace: message.id.namespace, customTag: customTag), id: message.id.id, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + } } for tag in message.globalTags { self.globalTagsTable.remove(tag, index: index) @@ -1619,6 +1629,8 @@ final class MessageHistoryTable: Table { self.customTagTable.remove(threadId: nil, tag: customTag, index: index) if let threadId = previousMessage.threadId { self.customTagTable.remove(threadId: threadId, tag: customTag, index: index) + + self.summaryTable.removeMessage(key: MessageHistoryTagsSummaryKey(tag: MessageTags(), peerId: index.id.peerId, threadId: threadId, namespace: index.id.namespace, customTag: customTag), id: index.id.id, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } self.summaryTable.removeMessage(key: MessageHistoryTagsSummaryKey(tag: MessageTags(), peerId: index.id.peerId, threadId: nil, namespace: index.id.namespace, customTag: customTag), id: index.id.id, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) @@ -1627,6 +1639,8 @@ final class MessageHistoryTable: Table { self.customTagTable.add(threadId: nil, tag: customTag, index: message.index) if let threadId = message.threadId { self.customTagTable.add(threadId: threadId, tag: customTag, index: message.index) + + self.summaryTable.addMessage(key: MessageHistoryTagsSummaryKey(tag: MessageTags(), peerId: message.id.peerId, threadId: threadId, namespace: message.id.namespace, customTag: customTag), id: message.id.id, isNewlyAdded: false, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } self.summaryTable.addMessage(key: MessageHistoryTagsSummaryKey(tag: MessageTags(), peerId: message.id.peerId, threadId: nil, namespace: message.id.namespace, customTag: customTag), id: message.id.id, isNewlyAdded: false, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) diff --git a/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift b/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift index e1148ef0d50..ae2ca6183f6 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift @@ -173,7 +173,9 @@ class MessageHistoryTagsSummaryTable: Table { } func getCustomTags(tag: MessageTags, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace) -> [MemoryBuffer] { - let peerKey = self.keyInternal(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: peerId, threadId: threadId, namespace: namespace, customTag: nil), allowShared: false) + let key = MessageHistoryTagsSummaryKey(tag: tag, peerId: peerId, threadId: threadId, namespace: namespace, customTag: nil) + + let peerKey = self.keyInternal(key: key, allowShared: false) let prefixLength = 4 + 8 + 4 + 8 var result: [MemoryBuffer] = [] self.valueBox.range(self.table, start: peerKey.predecessor, end: peerKey.successor, keys: { key in @@ -187,6 +189,17 @@ class MessageHistoryTagsSummaryTable: Table { } return true }, limit: 0) + + for updatedKey in self.updatedKeys { + if updatedKey.peerId == peerId && updatedKey.tag == tag && updatedKey.threadId == threadId && updatedKey.namespace == namespace { + if let customTag = updatedKey.customTag { + if !result.contains(customTag) { + result.append(customTag) + } + } + } + } + return result } @@ -218,7 +231,6 @@ class MessageHistoryTagsSummaryTable: Table { func removeMessage(key: MessageHistoryTagsSummaryKey, id: MessageId.Id, updatedSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) { if let current = self.get(key) { if current.count == 0 { - //self.invalidateTable.insert(InvalidatedMessageHistoryTagsSummaryKey(peerId: key.peerId, namespace: key.namespace, tagMask: key.tag), operations: &invalidateSummaries) } else { self.set(key, summary: current.withAddedCount(-1), updatedSummaries: &updatedSummaries) } diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 91581bce14c..ee55e89dec8 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -355,7 +355,11 @@ final class MutableMessageHistoryView { self.combinedReadStates = combinedReadStates self.transientReadStates = transientReadStates self.tag = tag - self.appendMessagesFromTheSameGroup = appendMessagesFromTheSameGroup + if case .customTag = tag { + self.appendMessagesFromTheSameGroup = true + } else { + self.appendMessagesFromTheSameGroup = appendMessagesFromTheSameGroup + } self.namespaces = namespaces self.fillCount = count self.topTaggedMessages = topTaggedMessages diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index c69f3a4562c..1fb2892f309 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -60,7 +60,7 @@ private extension MessageHistoryInput { case let .tag(value): shouldAddFromSameGroup = value.appendMessagesFromTheSameGroup case .customTag: - shouldAddFromSameGroup = false + shouldAddFromSameGroup = true } } if shouldAddFromSameGroup { @@ -75,20 +75,48 @@ private extension MessageHistoryInput { if var group = postbox.messageHistoryTable.getMessageGroup(at: items[index].index, limit: 20), group.count > 1 { switch direction { case .lowToHigh: - group.sort(by: { lhs, rhs in - return lhs.index < rhs.index - }) + group = group.filter { item in + if includeFrom { + return item.index >= fromIndex && item.index < toIndex + } else { + return item.index > fromIndex && item.index < toIndex + } + } case .highToLow: - group.sort(by: { lhs, rhs in - return lhs.index > rhs.index - }) + group = group.filter { item in + if includeFrom { + return item.index >= toIndex && item.index < fromIndex + } else { + return item.index > toIndex && item.index < fromIndex + } + } } - items.replaceSubrange(index ..< index + 1, with: group) + + items.remove(at: index) + var insertIndex = index + for item in group { + if !items.contains(where: { $0.id == item.id }) { + items.insert(item, at: insertIndex) + insertIndex += 1 + } + } + switch direction { case .lowToHigh: - items.removeFirst(group.count - 1) + items.sort(by: { $0.index < $1.index }) case .highToLow: - items.removeLast(group.count - 1) + items.sort(by: { $0.index > $1.index }) + } + assert(Set(items.map({ $0.stableId })).count == items.count) + + if items.count > limit { + let overLimit = items.count - limit + switch direction { + case .lowToHigh: + items.removeFirst(overLimit) + case .highToLow: + items.removeLast(overLimit) + } } } } @@ -2054,7 +2082,7 @@ enum HistoryViewState { init(postbox: PostboxImpl, inputAnchor: HistoryViewInputAnchor, tag: HistoryViewInputTag?, appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, ignoreMessagesInTimestampRange: ClosedRange?, halfLimit: Int, locations: MessageHistoryViewInput) { switch inputAnchor { case let .index(index): - self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) + self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) case .lowerBound: self = .loaded(HistoryViewLoadedState(anchor: .lowerBound, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) case .upperBound: diff --git a/submodules/Postbox/Sources/MutableMessageHistoryCustomTagSummariesView.swift b/submodules/Postbox/Sources/MutableMessageHistoryCustomTagSummariesView.swift index e7be5c75fa3..f356e5c8a18 100644 --- a/submodules/Postbox/Sources/MutableMessageHistoryCustomTagSummariesView.swift +++ b/submodules/Postbox/Sources/MutableMessageHistoryCustomTagSummariesView.swift @@ -2,12 +2,14 @@ import Foundation final class MutableMessageHistoryCustomTagSummariesView: MutablePostboxView { private let peerId: PeerId + private let threadId: Int64? private let namespace: MessageId.Namespace fileprivate var tags: [MemoryBuffer: Int] = [:] - init(postbox: PostboxImpl, peerId: PeerId, namespace: MessageId.Namespace) { + init(postbox: PostboxImpl, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace) { self.peerId = peerId + self.threadId = threadId self.namespace = namespace self.reload(postbox: postbox) @@ -16,8 +18,8 @@ final class MutableMessageHistoryCustomTagSummariesView: MutablePostboxView { private func reload(postbox: PostboxImpl) { self.tags.removeAll() - for tag in postbox.messageHistoryTagsSummaryTable.getCustomTags(tag: [], peerId: self.peerId, threadId: nil, namespace: self.namespace) { - if let summary = postbox.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: [], peerId: self.peerId, threadId: nil, namespace: self.namespace, customTag: tag)) { + for tag in postbox.messageHistoryTagsSummaryTable.getCustomTags(tag: [], peerId: self.peerId, threadId: self.threadId, namespace: self.namespace) { + if let summary = postbox.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: [], peerId: self.peerId, threadId: self.threadId, namespace: self.namespace, customTag: tag)) { if summary.count > 0 { self.tags[tag] = Int(summary.count) } @@ -29,7 +31,7 @@ final class MutableMessageHistoryCustomTagSummariesView: MutablePostboxView { var hasChanges = false for key in transaction.currentUpdatedMessageTagSummaries.keys { - if key.peerId == self.peerId && key.namespace == self.namespace && key.customTag != nil { + if key.peerId == self.peerId && key.namespace == self.namespace && key.customTag != nil && key.threadId == self.threadId { hasChanges = true break } diff --git a/submodules/Postbox/Sources/SeedConfiguration.swift b/submodules/Postbox/Sources/SeedConfiguration.swift index f389647aa2a..66e0d820945 100644 --- a/submodules/Postbox/Sources/SeedConfiguration.swift +++ b/submodules/Postbox/Sources/SeedConfiguration.swift @@ -80,6 +80,7 @@ public final class SeedConfiguration { public let isPeerUpgradeMessage: (Message) -> Bool public let automaticThreadIndexInfo: (PeerId, Int64) -> StoredMessageHistoryThreadInfo? public let customTagsFromAttributes: ([MessageAttribute]) -> [MemoryBuffer] + public let displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey public init( globalMessageIdsPeerIdNamespaces: Set, @@ -109,7 +110,8 @@ public final class SeedConfiguration { decodeDisplayPeerAsRegularChat: @escaping (CachedPeerData) -> Bool, isPeerUpgradeMessage: @escaping (Message) -> Bool, automaticThreadIndexInfo: @escaping (PeerId, Int64) -> StoredMessageHistoryThreadInfo?, - customTagsFromAttributes: @escaping ([MessageAttribute]) -> [MemoryBuffer] + customTagsFromAttributes: @escaping ([MessageAttribute]) -> [MemoryBuffer], + displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey ) { self.globalMessageIdsPeerIdNamespaces = globalMessageIdsPeerIdNamespaces self.initializeChatListWithHole = initializeChatListWithHole @@ -135,5 +137,6 @@ public final class SeedConfiguration { self.isPeerUpgradeMessage = isPeerUpgradeMessage self.automaticThreadIndexInfo = automaticThreadIndexInfo self.customTagsFromAttributes = customTagsFromAttributes + self.displaySavedMessagesAsTopicListPreferencesKey = displaySavedMessagesAsTopicListPreferencesKey } } diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index ffbd9a35ac8..aed2fb3e302 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -13,7 +13,7 @@ public enum PostboxViewKey: Hashable { case invalidatedMessageHistoryTagSummaries(peerId: PeerId?, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace) case pendingMessageActionsSummary(type: PendingMessageActionType, peerId: PeerId, namespace: MessageId.Namespace) case historyTagSummaryView(tag: MessageTags, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, customTag: MemoryBuffer?) - case historyCustomTagSummariesView(peerId: PeerId, namespace: MessageId.Namespace) + case historyCustomTagSummariesView(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace) case cachedPeerData(peerId: PeerId) case unreadCounts(items: [UnreadMessageCountsItem]) case combinedReadState(peerId: PeerId, handleThreads: Bool) @@ -85,8 +85,9 @@ public enum PostboxViewKey: Hashable { hasher.combine(threadId) hasher.combine(namespace) hasher.combine(customTag) - case let .historyCustomTagSummariesView(peerId, namespace): + case let .historyCustomTagSummariesView(peerId, threadId, namespace): hasher.combine(peerId) + hasher.combine(threadId) hasher.combine(namespace) case let .cachedPeerData(peerId): hasher.combine(peerId) @@ -241,8 +242,8 @@ public enum PostboxViewKey: Hashable { } else { return false } - case let .historyCustomTagSummariesView(peerId, namespace): - if case .historyCustomTagSummariesView(peerId, namespace) = rhs { + case let .historyCustomTagSummariesView(peerId, threadId, namespace): + if case .historyCustomTagSummariesView(peerId, threadId, namespace) = rhs { return true } else { return false @@ -487,8 +488,8 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutablePendingMessageActionsSummaryView(postbox: postbox, type: type, peerId: peerId, namespace: namespace) case let .historyTagSummaryView(tag, peerId, threadId, namespace, customTag): return MutableMessageHistoryTagSummaryView(postbox: postbox, tag: tag, peerId: peerId, threadId: threadId, namespace: namespace, customTag: customTag) - case let .historyCustomTagSummariesView(peerId, namespace): - return MutableMessageHistoryCustomTagSummariesView(postbox: postbox, peerId: peerId, namespace: namespace) + case let .historyCustomTagSummariesView(peerId, threadId, namespace): + return MutableMessageHistoryCustomTagSummariesView(postbox: postbox, peerId: peerId, threadId: threadId, namespace: namespace) case let .cachedPeerData(peerId): return MutableCachedPeerDataView(postbox: postbox, peerId: peerId) case let .unreadCounts(items): diff --git a/submodules/PremiumUI/Resources/tag.png b/submodules/PremiumUI/Resources/tag.png new file mode 100644 index 00000000000..a3a71e26298 Binary files /dev/null and b/submodules/PremiumUI/Resources/tag.png differ diff --git a/submodules/PremiumUI/Resources/tag.scn b/submodules/PremiumUI/Resources/tag.scn new file mode 100644 index 00000000000..03eef5673cc Binary files /dev/null and b/submodules/PremiumUI/Resources/tag.scn differ diff --git a/submodules/PremiumUI/Sources/BadgeStarsView.swift b/submodules/PremiumUI/Sources/BadgeStarsView.swift index 6ca220ce3a1..f8f80a116d3 100644 --- a/submodules/PremiumUI/Sources/BadgeStarsView.swift +++ b/submodules/PremiumUI/Sources/BadgeStarsView.swift @@ -111,3 +111,57 @@ final class EmojiStarsView: UIView, PhoneDemoDecorationView { self.sceneView.frame = CGRect(origin: .zero, size: frame.size) } } + +final class TagStarsView: UIView, PhoneDemoDecorationView { + private let sceneView: SCNView + + private var leftParticles: SCNNode? + private var rightParticles: SCNNode? + + override init(frame: CGRect) { + self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size)) + self.sceneView.backgroundColor = .clear + if let url = getAppBundle().url(forResource: "tag", withExtension: "scn") { + self.sceneView.scene = try? SCNScene(url: url, options: nil) + } + self.sceneView.isUserInteractionEnabled = false + self.sceneView.preferredFramesPerSecond = 60 + + super.init(frame: frame) + + self.alpha = 0.0 + + self.addSubview(self.sceneView) + + self.leftParticles = self.sceneView.scene?.rootNode.childNode(withName: "leftParticles", recursively: false) + self.rightParticles = self.sceneView.scene?.rootNode.childNode(withName: "rightParticles", recursively: false) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setVisible(_ visible: Bool) { + if visible, let leftParticles = self.leftParticles, let rightParticles = self.rightParticles, leftParticles.parent == nil { + self.sceneView.scene?.rootNode.addChildNode(leftParticles) + self.sceneView.scene?.rootNode.addChildNode(rightParticles) + } + + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) + transition.updateAlpha(layer: self.layer, alpha: visible ? 0.5 : 0.0, completion: { [weak self] finished in + if let strongSelf = self, finished && !visible && strongSelf.leftParticles?.parent != nil { + strongSelf.leftParticles?.removeFromParentNode() + strongSelf.rightParticles?.removeFromParentNode() + } + }) + } + + func resetAnimation() { + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.sceneView.frame = CGRect(origin: .zero, size: frame.size) + } +} diff --git a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift index 51be389a876..5a918550c98 100644 --- a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift +++ b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift @@ -370,6 +370,7 @@ final class PhoneDemoComponent: Component { case badgeStars case emoji case hello + case tag } enum Model { @@ -539,6 +540,13 @@ final class PhoneDemoComponent: Component { self.decorationView = starsView self.decorationContainerView.addSubview(starsView) } + case .tag: + if let _ = self.decorationView as? TagStarsView { + } else { + let starsView = TagStarsView(frame: self.decorationContainerView.bounds) + self.decorationView = starsView + self.decorationContainerView.addSubview(starsView) + } } self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position) diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 575aa94ab46..ba3b2a1c05e 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -477,7 +477,7 @@ private final class DemoSheetContent: CombinedComponent { 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] + 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 } @@ -981,6 +981,25 @@ private final class DemoSheetContent: CombinedComponent { ) ) ) + availableItems[.messageTags] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.messageTags, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["saved_tags"], + decoration: .tag + )), + title: strings.Premium_MessageTags, + text: strings.Premium_MessageTagsInfo, + textColor: textColor + ) + ) + ) + ) var items: [DemoPagerComponent.Item] = component.order.compactMap { availableItems[$0] } let index: Int @@ -1083,6 +1102,8 @@ private final class DemoSheetContent: CombinedComponent { buttonText = strings.Premium_Wallpaper_Proceed case .colors: buttonText = strings.Premium_Colors_Proceed + case .messageTags: + buttonText = strings.Premium_MessageTags_Proceed default: buttonText = strings.Common_OK } @@ -1118,9 +1139,9 @@ private final class DemoSheetContent: CombinedComponent { text = strings.Premium_ColorsInfo case .wallpapers: text = strings.Premium_WallpapersInfo - case .doubleLimits: - text = "" - case .stories: + case .messageTags: + text = strings.Premium_MessageTagsInfo + case .doubleLimits, .stories: text = "" } @@ -1338,6 +1359,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { case stories case colors case wallpapers + case messageTags } public enum Source: Equatable { diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index f217d65bb1d..528853b9173 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -429,6 +429,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { UIColor(rgb: 0xcb3e6d), UIColor(rgb: 0xbc4395), UIColor(rgb: 0xab4ac4), + UIColor(rgb: 0xa34cd7), UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), @@ -521,6 +522,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { demoSubject = .colors case .wallpapers: demoSubject = .wallpapers + case .messageTags: + demoSubject = .messageTags } let buttonText: String diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 42e05584957..6c055ba853b 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -233,6 +233,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .storiesHigherQuality: + if case .storiesHigherQuality = rhs { + return true + } else { + return false + } case let .channelBoost(peerId): if case .channelBoost(peerId) = rhs { return true @@ -269,6 +275,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .messageTags: + if case .messageTags = rhs { + return true + } else { + return false + } } } @@ -305,12 +317,14 @@ public enum PremiumSource: Equatable { case storiesFormatting case storiesExpirationDurations case storiesSuggestedReactions + case storiesHigherQuality case channelBoost(EnginePeer.Id) case nameColor case similarChannels case wallpapers case presence case readTime + case messageTags var identifier: String? { switch self { @@ -382,6 +396,8 @@ public enum PremiumSource: Equatable { return "stories__expiration_durations" case .storiesSuggestedReactions: return "stories__suggested_reactions" + case .storiesHigherQuality: + return "stories__quality" case let .channelBoost(peerId): return "channel_boost__\(peerId.id._internalGetInt64Value())" case .nameColor: @@ -394,6 +410,8 @@ public enum PremiumSource: Equatable { return "presence" case .readTime: return "read_time" + case .messageTags: + return "saved_tags" } } } @@ -416,6 +434,7 @@ public enum PremiumPerk: CaseIterable { case stories case colors case wallpapers + case messageTags public static var allCases: [PremiumPerk] { return [ @@ -435,7 +454,8 @@ public enum PremiumPerk: CaseIterable { .translation, .stories, .colors, - .wallpapers + .wallpapers, + .messageTags ] } @@ -485,6 +505,8 @@ public enum PremiumPerk: CaseIterable { return "peer_colors" case .wallpapers: return "wallpapers" + case .messageTags: + return "saved_tags" } } @@ -524,6 +546,8 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_Colors case .wallpapers: return strings.Premium_Wallpapers + case .messageTags: + return strings.Premium_MessageTags } } @@ -563,6 +587,8 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_ColorsInfo case .wallpapers: return strings.Premium_WallpapersInfo + case .messageTags: + return strings.Premium_MessageTagsInfo } } @@ -602,6 +628,8 @@ public enum PremiumPerk: CaseIterable { return "Premium/Perk/Colors" case .wallpapers: return "Premium/Perk/Wallpapers" + case .messageTags: + return "Premium/Perk/MessageTags" } } } @@ -610,22 +638,23 @@ struct PremiumIntroConfiguration { static var defaultValue: PremiumIntroConfiguration { return PremiumIntroConfiguration(perks: [ .stories, - .doubleLimits, .moreUpload, + .doubleLimits, + .voiceToText, .fasterDownload, .translation, - .voiceToText, - .noAds, + .animatedEmoji, .emojiStatus, + .messageTags, .colors, .wallpapers, - .uniqueReactions, - .premiumStickers, - .animatedEmoji, - .advancedChatManagement, .profileBadge, + .advancedChatManagement, + .noAds, + .appIcons, + .uniqueReactions, .animatedUserpics, - .appIcons + .premiumStickers ]) } @@ -661,6 +690,9 @@ struct PremiumIntroConfiguration { if !perks.contains(.colors) { perks.append(.colors) } + if !perks.contains(.messageTags) { + perks.append(.messageTags) + } #endif return PremiumIntroConfiguration(perks: perks) } else { @@ -1621,8 +1653,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { self.newPerksDisposable = combineLatest(queue: Queue.mainQueue(), ApplicationSpecificNotice.dismissedPremiumAppIconsBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedPremiumWallpapersBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedPremiumColorsBadge(accountManager: context.sharedContext.accountManager) - ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge in + ApplicationSpecificNotice.dismissedPremiumColorsBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedMessageTagsBadge(accountManager: context.sharedContext.accountManager) + ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge in guard let self else { return } @@ -1633,6 +1666,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if !dismissedPremiumColorsBadge { newPerks.append(PremiumPerk.colors.identifier) } + if !dismissedMessageTagsBadge { + newPerks.append(PremiumPerk.messageTags.identifier) + } self.newPerks = newPerks self.updated() }) @@ -1812,6 +1848,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0xcb3e6d), UIColor(rgb: 0xbc4395), UIColor(rgb: 0xab4ac4), + UIColor(rgb: 0xa34cd7), UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), @@ -2035,6 +2072,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { case .wallpapers: demoSubject = .wallpapers let _ = ApplicationSpecificNotice.setDismissedPremiumWallpapersBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() + case .messageTags: + demoSubject = .messageTags + let _ = ApplicationSpecificNotice.setDismissedMessageTagsBadge(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 5143f399c5d..829c0e8e0f8 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -914,11 +914,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" if component.count >= premiumLimit { - badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(limit) / CGFloat(premiumLimit)) } else { - badgeGraphPosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) } - badgePosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgePosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) if !state.isPremium && badgePosition > 0.5 { string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string @@ -952,11 +952,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" if component.count >= premiumLimit { - badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(limit) / CGFloat(premiumLimit)) } else { - badgeGraphPosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) } - badgePosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgePosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) if isPremiumDisabled { badgeText = "\(limit)" @@ -977,11 +977,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = count > limit ? "\(limit)" : "" premiumValue = count >= premiumLimit ? "" : "\(premiumLimit)" if count >= premiumLimit { - badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(limit) / CGFloat(premiumLimit)) } else { - badgeGraphPosition = max(0.15, CGFloat(count) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(count) / CGFloat(premiumLimit)) } - badgePosition = max(0.15, CGFloat(count) / CGFloat(premiumLimit)) + badgePosition = max(0.35, CGFloat(count) / CGFloat(premiumLimit)) if isPremiumDisabled { badgeText = "\(limit)" @@ -998,11 +998,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" if component.count >= premiumLimit { - badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(limit) / CGFloat(premiumLimit)) } else { - badgeGraphPosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgeGraphPosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) } - badgePosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit)) + badgePosition = max(0.35, CGFloat(component.count) / CGFloat(premiumLimit)) if isPremiumDisabled { badgeText = "\(limit)" @@ -1033,7 +1033,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.15, min(0.85, CGFloat(component.count) / CGFloat(premiumLimit))) + badgePosition = max(0.35, min(0.85, CGFloat(component.count) / CGFloat(premiumLimit))) badgeGraphPosition = badgePosition buttonAnimationName = nil diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 43617b719f5..e743fe4bfdf 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -742,6 +742,25 @@ public class PremiumLimitsListScreen: ViewController { ) ) ) + availableItems[.messageTags] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.messageTags, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + model: .island, + videoFile: configuration.videos["saved_tags"], + decoration: .tag + )), + title: strings.Premium_MessageTags, + text: strings.Premium_MessageTagsInfo, + textColor: textColor + ) + ) + ) + ) if let order = controller.order { var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] } diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift index 3196b390fc8..3821086a4c5 100644 --- a/submodules/PremiumUI/Sources/StoriesPageComponent.swift +++ b/submodules/PremiumUI/Sources/StoriesPageComponent.swift @@ -312,13 +312,14 @@ private final class StoriesListComponent: CombinedComponent { let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings let colors = [ - UIColor(rgb: 0x0275f3), - UIColor(rgb: 0x8698ff), - UIColor(rgb: 0xc871ff), - UIColor(rgb: 0xc356ad), - UIColor(rgb: 0xe85c44), - UIColor(rgb: 0xff932b), - UIColor(rgb: 0xe9af18) + UIColor(rgb: 0x007aff), + UIColor(rgb: 0x798aff), + UIColor(rgb: 0xac64f3), + UIColor(rgb: 0xc456ae), + UIColor(rgb: 0xe95d44), + UIColor(rgb: 0xf2822a), + UIColor(rgb: 0xe79519), + UIColor(rgb: 0xe7ad19) ] let titleColor = theme.list.itemPrimaryTextColor @@ -367,6 +368,20 @@ private final class StoriesListComponent: CombinedComponent { ) ) + items.append( + AnyComponentWithIdentity( + id: "quality", + component: AnyComponent(ParagraphComponent( + title: strings.Premium_Stories_Quality_Title, + titleColor: titleColor, + text: strings.Premium_Stories_Quality_Text, + textColor: textColor, + iconName: "Premium/Stories/Quality", + iconColor: colors[2] + )) + ) + ) + items.append( AnyComponentWithIdentity( id: "views", @@ -376,7 +391,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Views_Text, textColor: textColor, iconName: "Premium/Stories/Views", - iconColor: colors[2] + iconColor: colors[3] )) ) ) @@ -390,7 +405,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Expiration_Text, textColor: textColor, iconName: "Premium/Stories/Expire", - iconColor: colors[3] + iconColor: colors[4] )) ) ) @@ -404,7 +419,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Save_Text, textColor: textColor, iconName: "Premium/Stories/Save", - iconColor: colors[4] + iconColor: colors[5] )) ) ) @@ -418,7 +433,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Captions_Text, textColor: textColor, iconName: "Premium/Stories/Caption", - iconColor: colors[5] + iconColor: colors[6] )) ) ) @@ -432,7 +447,7 @@ private final class StoriesListComponent: CombinedComponent { text: strings.Premium_Stories_Format_Text, textColor: textColor, iconName: "Premium/Stories/Format", - iconColor: colors[6] + iconColor: colors[7] )) ) ) diff --git a/submodules/ReactionSelectionNode/BUILD b/submodules/ReactionSelectionNode/BUILD index a562931daa3..9eb229a0115 100644 --- a/submodules/ReactionSelectionNode/BUILD +++ b/submodules/ReactionSelectionNode/BUILD @@ -35,6 +35,8 @@ swift_library( "//submodules/GZip:GZip", "//submodules/ShimmerEffect:ShimmerEffect", "//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage", + "//submodules/Components/BalancedTextComponent", + "//submodules/Markdown", ], visibility = [ "//visibility:public", diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index e687516ee1c..2cf408ee88b 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -21,6 +21,8 @@ import MultiAnimationRenderer import EmojiTextAttachmentView import TextFormat import GZip +import BalancedTextComponent +import Markdown public final class ReactionItem { public struct Reaction: Equatable { @@ -124,6 +126,8 @@ private final class TitleLabelView: UIView { let contentView = ComponentView() let tintContentView = ComponentView() + var action: (() -> Void)? + override init(frame: CGRect) { super.init(frame: frame) } @@ -132,18 +136,64 @@ private final class TitleLabelView: UIView { fatalError("init(coder:) has not been implemented") } - func update(size: CGSize, text: String, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { + func update(width: CGFloat, text: String, theme: PresentationTheme, transition: ContainedViewLayoutTransition) -> CGFloat { + let foregroundColor: UIColor + if theme.overallDarkAppearance { + foregroundColor = UIColor(white: 1.0, alpha: 0.5) + } else { + foregroundColor = UIColor(white: 0.5, alpha: 0.9) + } + + let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: foregroundColor) + let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: foregroundColor) + let link = MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.list.itemAccentColor, additionalAttributes: ["URL": true as NSNumber]) + let attributes = MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in + return nil + }) + + let tintBody = MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white) + let tintBold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: .white) + let tintLink = MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white, additionalAttributes: [TelegramTextAttributes.URL: true as NSNumber]) + let tintAttributes = MarkdownAttributes(body: tintBody, bold: tintBold, link: tintLink, linkAttribute: { _ in + return (TelegramTextAttributes.URL, "") + }) + let contentSize = self.contentView.update( transition: .immediate, - component: AnyComponent(Text(text: text, font: Font.regular(13.0), color: UIColor(white: 1.0, alpha: 0.2))), + component: AnyComponent(BalancedTextComponent( + text: .markdown(text: text, attributes: attributes), + balanced: true, + horizontalAlignment: .center, + maximumNumberOfLines: 0, + highlightColor: theme.list.itemAccentColor.withMultipliedAlpha(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + }, tapAction: { [weak self] attributes, _ in + guard let self else { + return + } + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + self.action?() + } + } + )), environment: {}, - containerSize: size + containerSize: CGSize(width: width - 8.0 * 2.0, height: 10000.0) ) let _ = self.tintContentView.update( transition: .immediate, - component: AnyComponent(Text(text: text, font: Font.regular(13.0), color: .white)), + component: AnyComponent(BalancedTextComponent( + text: .markdown(text: text, attributes: tintAttributes), + balanced: true, + horizontalAlignment: .center, + maximumNumberOfLines: 0 + )), environment: {}, - containerSize: size + containerSize: CGSize(width: width - 8.0 * 2.0, height: 10000.0) ) if let contentView = self.contentView.view { @@ -151,8 +201,10 @@ private final class TitleLabelView: UIView { contentView.layer.rasterizationScale = UIScreenScale self.addSubview(contentView) } - transition.updateFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - contentSize.width) / 2.0), y: 6.0), size: contentSize)) + transition.updateFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - contentSize.width) / 2.0), y: 6.0), size: contentSize)) } + + return 6.0 + contentSize.height } } @@ -245,7 +297,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { private let expandItemView: ExpandItemView? private let title: String? + private let reactionsLocked: Bool private var titleLabelView: TitleLabelView? + private var titleLabelHeight: CGFloat? private var reactionSelectionComponentHost: ComponentView? @@ -373,7 +427,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } - public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set, title: String? = nil, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) { + public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set, title: String? = nil, reactionsLocked: Bool, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) { self.context = context self.presentationData = presentationData self.items = items @@ -382,6 +436,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.isExpandedUpdated = isExpandedUpdated self.requestLayout = requestLayout self.requestUpdateOverlayWantsToBeBelowKeyboard = requestUpdateOverlayWantsToBeBelowKeyboard + self.reactionsLocked = reactionsLocked self.animationCache = animationCache self.animationRenderer = MultiAnimationRendererImpl() @@ -451,11 +506,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { context.setFillColor(shadowColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: gradientWidth - 1.0, dy: gradientWidth - 1.0)) })?.stretchableImage(withLeftCapWidth: Int(46.0 / 2.0), topCapHeight: Int(46.0 / 2.0)) - if self.getEmojiContent == nil { + if self.getEmojiContent == nil || self.reactionsLocked { self.contentContainer.view.mask = self.contentContainerMask } - if getEmojiContent != nil { + if getEmojiContent != nil && !self.reactionsLocked { let expandItemView = ExpandItemView() self.expandItemView = expandItemView @@ -470,7 +525,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { let titleLabelView = TitleLabelView(frame: CGRect()) self.titleLabelView = titleLabelView self.contentContainer.view.addSubview(titleLabelView) - self.contentTopInset = 24.0 } self.alwaysAllowPremiumReactions = alwaysAllowPremiumReactions @@ -485,6 +539,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.addSubnode(self.contentContainer) self.addSubnode(self.previewingItemContainer) + if let titleLabelView = self.titleLabelView { + titleLabelView.action = { [weak self] in + guard let self else { + return + } + self.premiumReactionsSelected?(nil) + } + } + self.availableReactionsDisposable = (context.engine.stickers.availableReactions() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] availableReactions in @@ -506,7 +569,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { }) } - if let getEmojiContent = getEmojiContent { + if let getEmojiContent = getEmojiContent, !self.reactionsLocked { let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) self.stableEmptyResultEmojiDisposable.set((self.context.account.postbox.combinedView(keys: [viewKey]) |> take(1) @@ -636,6 +699,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { longPressRecognizer.minimumPressDuration = 0.2 self.longPressRecognizer = longPressRecognizer self.view.addGestureRecognizer(longPressRecognizer) + + if self.allPresetReactionsAreAvailable { + longPressRecognizer.isEnabled = false + } } @objc private func horizontalExpandGesture(_ recognizer: UIPanGestureRecognizer) { @@ -812,7 +879,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { let effectiveItemSpacing: CGFloat = minItemSpacing + (1.0 - compressionFactor) * (itemSpacing - minItemSpacing) var topVisibleItems: Int - if self.getEmojiContent != nil { + if !self.reactionsLocked && self.getEmojiContent != nil { topVisibleItems = min(self.items.count, itemLayout.visibleItemCount) } else { topVisibleItems = self.items.count @@ -912,7 +979,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { itemTransition = .immediate if case let .reaction(item) = self.items[i] { - itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: loopIdle) + itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: loopIdle, isLocked: self.reactionsLocked) maskNode = nil } else { itemNode = PremiumReactionsNode(theme: self.presentationData.theme) @@ -956,7 +1023,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } - if self.getEmojiContent != nil && i == itemLayout.visibleItemCount - 1 { + if !self.reactionsLocked && self.getEmojiContent != nil && i == itemLayout.visibleItemCount - 1 { itemFrame.origin.x -= (1.0 - compressionFactor) * selectionItemFrame.width * 0.5 selectionItemFrame.origin.x -= (1.0 - compressionFactor) * selectionItemFrame.width * 0.5 itemNode.isUserInteractionEnabled = false @@ -997,7 +1064,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion && !self.reduceMotion) } - if self.getEmojiContent != nil, i == itemLayout.visibleItemCount - 1, let itemNode = itemNode as? ReactionNode { + if !self.reactionsLocked, self.getEmojiContent != nil, i == itemLayout.visibleItemCount - 1, let itemNode = itemNode as? ReactionNode { let itemScale: CGFloat = 0.001 * (1.0 - compressionFactor) + normalItemScale * compressionFactor transition.updateSublayerTransformScale(node: itemNode, scale: itemScale) transition.updateTransformScale(layer: itemNode.selectionView.layer, scale: CGPoint(x: itemScale, y: itemScale)) @@ -1018,22 +1085,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } - if let title = self.title, let titleLabelView = self.titleLabelView { - let baseTitleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.scrollNode.view.bounds.width, height: 20.0)) - - transition.updateFrame(view: titleLabelView, frame: baseTitleFrame) - titleLabelView.update(size: baseTitleFrame.size, text: title, theme: self.presentationData.theme, transition: transition) - transition.updateAlpha(layer: titleLabelView.layer, alpha: self.isExpanded ? 0.0 : 1.0) - if let titleView = titleLabelView.contentView.view, let tintContentView = titleLabelView.tintContentView.view { - if tintContentView.superview == nil { - tintContentView.layer.rasterizationScale = UIScreenScale - self.contentTintContainer.view.addSubview(tintContentView) - } - transition.updateFrame(view: tintContentView, frame: titleView.frame.offsetBy(dx: baseTitleFrame.minX, dy: baseTitleFrame.minY)) - transition.updateAlpha(layer: tintContentView.layer, alpha: self.isExpanded ? 0.0 : 1.0) - } - } - if let expandItemView = self.expandItemView { let expandItemSize: CGFloat let expandTintOffset: CGFloat @@ -1103,7 +1154,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { var visibleContentWidth: CGFloat var completeContentWidth: CGFloat - if self.getEmojiContent != nil { + if !self.reactionsLocked && self.getEmojiContent != nil { let totalItemSlotCount = self.items.count + 1 var maxRowItemCount = Int(floor((size.width - sideInset * 2.0 - externalSideInset * 2.0 - itemSpacing) / (itemSize + itemSpacing))) @@ -1137,6 +1188,28 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } + if let title = self.title, let titleLabelView = self.titleLabelView { + let titleLabelHeight = titleLabelView.update(width: visibleContentWidth, text: title, theme: self.presentationData.theme, transition: transition) + self.titleLabelHeight = titleLabelHeight + + let baseTitleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: visibleContentWidth, height: titleLabelHeight)) + transition.updateFrame(view: titleLabelView, frame: baseTitleFrame) + + transition.updateAlpha(layer: titleLabelView.layer, alpha: self.isExpanded ? 0.0 : 1.0) + if let titleView = titleLabelView.contentView.view, let tintContentView = titleLabelView.tintContentView.view { + if tintContentView.superview == nil { + tintContentView.layer.rasterizationScale = UIScreenScale + self.contentTintContainer.view.addSubview(tintContentView) + } + transition.updateFrame(view: tintContentView, frame: titleView.frame.offsetBy(dx: baseTitleFrame.minX, dy: baseTitleFrame.minY)) + transition.updateAlpha(layer: tintContentView.layer, alpha: self.isExpanded ? 0.0 : 1.0) + } + + if !self.isExpanded { + self.contentTopInset = titleLabelHeight + } + } + let contentHeight = verticalInset * 2.0 + rowHeight var backgroundInsets = insets @@ -1185,7 +1258,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { itemSpacing: itemSpacing ) - if (self.isExpanded || self.reactionSelectionComponentHost != nil), let _ = self.getEmojiContent { + if (self.isExpanded || self.reactionSelectionComponentHost != nil), let _ = self.getEmojiContent, !self.reactionsLocked { let reactionSelectionComponentHost: ComponentView var componentTransition = Transition(transition) if let current = self.reactionSelectionComponentHost { @@ -1427,6 +1500,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { if case .locked = item.icon { strongSelf.premiumReactionsSelected?(reactionItem.stillAnimation) + } else if strongSelf.reactionsLocked { + strongSelf.premiumReactionsSelected?(reactionItem.stillAnimation) } else { strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem) strongSelf.reactionSelected?(updateReaction, isLongPress) @@ -1449,6 +1524,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem) if case .locked = item.icon { strongSelf.premiumReactionsSelected?(reactionItem.stillAnimation) + } else if strongSelf.reactionsLocked { + strongSelf.premiumReactionsSelected?(reactionItem.stillAnimation) } else { strongSelf.reactionSelected?(reactionItem.updateMessageReaction, isLongPress) } @@ -1840,7 +1917,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { guard let itemNode = self.visibleItemNodes[i] else { continue } - if let itemLayout = self.itemLayout, self.getEmojiContent != nil, i == itemLayout.visibleItemCount - 1 { + if let itemLayout = self.itemLayout, !self.reactionsLocked, self.getEmojiContent != nil, i == itemLayout.visibleItemCount - 1 { itemNode.appear(animated: false) continue } @@ -1899,6 +1976,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { itemNode.layer.animateAlpha(from: itemNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) } + if let titleLabelView = self.titleLabelView { + titleLabelView.layer.animateAlpha(from: titleLabelView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + if let reactionComponentView = self.reactionSelectionComponentHost?.view { reactionComponentView.alpha = 0.0 reactionComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) @@ -2007,7 +2088,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } if let customReactionSource = self.customReactionSource { - let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: false, useDirectRendering: false) + let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: false, isLocked: false, useDirectRendering: false) if let contents = customReactionSource.layer.contents { itemNode.setCustomContents(contents: contents) } @@ -2335,6 +2416,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.isExpanded, let titleLabelView = self.titleLabelView { + if let result = titleLabelView.hitTest(self.view.convert(point, to: titleLabelView), with: event) { + return result + } + } + let contentPoint = self.contentContainer.view.convert(point, from: self.view) if self.contentContainer.bounds.contains(contentPoint) { return self.contentContainer.hitTest(contentPoint, with: event) @@ -2416,6 +2503,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { case .ended: let point = recognizer.location(in: self.view) + if self.isExpanded { + return + } if let expandItemView = self.expandItemView, expandItemView.bounds.contains(self.view.convert(point, to: self.expandItemView)) { self.animateFromExtensionDistance = self.contentTopInset * 2.0 + self.extensionDistance self.contentTopInset = 0.0 @@ -2428,6 +2518,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { case let .reaction(reactionItem): if case .custom = reactionItem.updateMessageReaction, let hasPremium = self.hasPremium, !hasPremium, !self.allPresetReactionsAreAvailable { self.premiumReactionsSelected?(reactionItem.stillAnimation) + } else if self.reactionsLocked { + self.premiumReactionsSelected?(reactionItem.stillAnimation) } else { self.reactionSelected?(reactionItem.updateMessageReaction, false) } @@ -2466,6 +2558,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } public func highlightGestureMoved(location: CGPoint, hover: Bool) { + if self.allPresetReactionsAreAvailable { + return + } + let highlightedReaction = self.previewReaction(at: location)?.reaction if self.highlightedReaction != highlightedReaction { self.highlightedReaction = highlightedReaction @@ -2485,10 +2581,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } public func highlightGestureFinished(performAction: Bool) { + if self.allPresetReactionsAreAvailable { + return + } + self.highlightGestureFinished(performAction: performAction, isLarge: false) } private func highlightGestureFinished(performAction: Bool, isLarge: Bool) { + if self.allPresetReactionsAreAvailable { + return + } + if let highlightedReaction = self.highlightedReaction { self.highlightedReaction = nil if performAction { @@ -2573,6 +2677,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { if let itemNode = itemNode as? ReactionNode, itemNode.item.reaction == reaction { if case .custom = itemNode.item.updateMessageReaction, let hasPremium = self.hasPremium, !hasPremium { self.premiumReactionsSelected?(itemNode.item.stillAnimation) + } else if self.reactionsLocked { + self.premiumReactionsSelected?(itemNode.item.stillAnimation) } else { self.reactionSelected?(itemNode.item.updateMessageReaction, isLarge) } @@ -2640,7 +2746,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode { itemNode = currentItemNode } else { let animationRenderer = MultiAnimationRendererImpl() - itemNode = ReactionNode(context: context, theme: theme, item: reaction, animationCache: animationCache, animationRenderer: animationRenderer, loopIdle: false) + itemNode = ReactionNode(context: context, theme: theme, item: reaction, animationCache: animationCache, animationRenderer: animationRenderer, loopIdle: false, isLocked: false) } self.itemNode = itemNode diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 709f8486345..a2032ff3096 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -48,11 +48,15 @@ protocol ReactionItemNode: ASDisplayNode { func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) } +private let lockedBackgroundImage: UIImage = generateFilledCircleImage(diameter: 12.0, color: .white)!.withRenderingMode(.alwaysTemplate) +private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) + public final class ReactionNode: ASDisplayNode, ReactionItemNode { let context: AccountContext let theme: PresentationTheme let item: ReactionItem private let loopIdle: Bool + private let isLocked: Bool private let hasAppearAnimation: Bool private let useDirectRendering: Bool @@ -66,6 +70,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { private var customContentsNode: ASDisplayNode? private var animationNode: AnimatedStickerNode? + private var lockBackgroundView: UIImageView? + private var lockIconView: UIImageView? + private var dismissedStillAnimationNodes: [AnimatedStickerNode] = [] private var fetchStickerDisposable: Disposable? @@ -91,11 +98,12 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { return self.staticAnimationNode.currentFrameImage != nil } - public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, loopIdle: Bool, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) { + public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, loopIdle: Bool, isLocked: Bool, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) { self.context = context self.theme = theme self.item = item self.loopIdle = loopIdle + self.isLocked = isLocked self.hasAppearAnimation = hasAppearAnimation self.useDirectRendering = useDirectRendering @@ -142,6 +150,25 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { if let applicationAnimation = item.applicationAnimation { self.fetchFullAnimationDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: applicationAnimation.resource)).start() } + + if self.isLocked { + let lockBackgroundView = UIImageView(image: lockedBackgroundImage) + self.lockBackgroundView = lockBackgroundView + self.view.addSubview(lockBackgroundView) + + let lockIconView = UIImageView(image: lockedBadgeIcon) + self.lockIconView = lockIconView + self.view.addSubview(lockIconView) + + if let staticAnimationNode = self.staticAnimationNode as? DefaultAnimatedStickerNodeImpl { + staticAnimationNode.frameColorUpdated = { [weak lockBackgroundView] color in + guard let lockBackgroundView else { + return + } + lockBackgroundView.tintColor = color + } + } + } } deinit { @@ -434,6 +461,17 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { if let customContentsNode = self.customContentsNode { transition.updateFrame(node: customContentsNode, frame: animationFrame) } + + if let lockBackgroundView = self.lockBackgroundView, let lockIconView = self.lockIconView, let iconImage = lockIconView.image { + let lockSize: CGFloat = 12.0 + let iconBackgroundFrame = CGRect(origin: CGPoint(x: animationFrame.maxX - lockSize, y: animationFrame.maxY - lockSize), size: CGSize(width: lockSize, height: lockSize)) + transition.updateFrame(view: lockBackgroundView, frame: iconBackgroundFrame) + + let iconFactor: CGFloat = 0.7 + let iconImageSize = CGSize(width: floor(iconImage.size.width * iconFactor), height: floor(iconImage.size.height * iconFactor)) + + transition.updateFrame(view: lockIconView, frame: CGRect(origin: CGPoint(x: iconBackgroundFrame.minX + floorToScreenPixels((iconBackgroundFrame.width - iconImageSize.width) * 0.5), y: iconBackgroundFrame.minY + floorToScreenPixels((iconBackgroundFrame.height - iconImageSize.height) * 0.5)), size: iconImageSize)) + } } } diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index 27cab694238..a3eaabd3ed8 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -214,7 +214,11 @@ public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal, _ s13: Signal, _ s14: Signal, _ s15: Signal, _ s16: Signal, _ s17: Signal, _ s18: Signal, _ s19: Signal, _ s20: Signal, _ s21: Signal, _ s22: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15), signalOfAny(s16), signalOfAny(s17), signalOfAny(s18), signalOfAny(s19), signalOfAny(s20), signalOfAny(s21), signalOfAny(s22)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15, values[15] as! T16, values[16] as! T17, values[17] as! T18, values[18] as! T19, values[19] as! T20, values[20] as! T21, values[21] as! T22) + }, initialValues: [:], queue: queue) +} public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index a100512a625..9b1a27d4bc0 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -97,7 +97,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case forwardPrivacy(PresentationTheme, String, String) case groupPrivacy(PresentationTheme, String, String) case voiceMessagePrivacy(PresentationTheme, String, String, Bool) - case messagePrivacy(Bool) + case messagePrivacy(PresentationTheme, Bool, Bool) case bioPrivacy(PresentationTheme, String, String) case selectivePrivacyInfo(PresentationTheme, String) case passcode(PresentationTheme, String, Bool, String) @@ -240,14 +240,14 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { } else { return false } - case let .voiceMessagePrivacy(lhsTheme, lhsText, lhsValue, lhsLocked): - if case let .voiceMessagePrivacy(rhsTheme, rhsText, rhsValue, rhsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsLocked == rhsLocked { + case let .voiceMessagePrivacy(lhsTheme, lhsText, lhsValue, lhsHasPremium): + if case let .voiceMessagePrivacy(rhsTheme, rhsText, rhsValue, rhsHasPremium) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsHasPremium == rhsHasPremium { return true } else { return false } - case let .messagePrivacy(value): - if case .messagePrivacy(value) = rhs { + case let .messagePrivacy(lhsTheme, lhsValue, lhsHasPremium): + if case let .messagePrivacy(rhsTheme, rhsValue, rhsHasPremium) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsHasPremium == rhsHasPremium { return true } else { return false @@ -390,12 +390,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openGroupsPrivacy() }) - case let .voiceMessagePrivacy(_, text, value, locked): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, labelStyle: locked ? .textWithIcon(UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon")!.precomposed()) : .text, sectionId: self.section, style: .blocks, action: { + case let .voiceMessagePrivacy(theme, text, value, hasPremium): + return ItemListDisclosureItem(presentationData: presentationData, title: text, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, action: { arguments.openVoiceMessagePrivacy() }) - case let .messagePrivacy(value): - return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.Settings_Privacy_Messages, label: !value ? presentationData.strings.Settings_Privacy_Messages_ValueEveryone : presentationData.strings.Settings_Privacy_Messages_ValueContactsAndPremium, sectionId: self.section, style: .blocks, action: { + case let .messagePrivacy(theme, value, hasPremium): + return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.Settings_Privacy_Messages, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: !value ? presentationData.strings.Settings_Privacy_Messages_ValueEveryone : presentationData.strings.Settings_Privacy_Messages_ValueContactsAndPremium, sectionId: self.section, style: .blocks, action: { arguments.openMessagePrivacy() }) case let .bioPrivacy(_, text, value): @@ -591,8 +591,8 @@ private func privacyAndSecurityControllerEntries( entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceCalls))) entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.groupInvitations))) if !isPremiumDisabled { - entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages), !isPremium)) - entries.append(.messagePrivacy(privacySettings.globalSettings.nonContactChatsRequirePremium)) + entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages), isPremium)) + entries.append(.messagePrivacy(presentationData.theme, privacySettings.globalSettings.nonContactChatsRequirePremium, isPremium)) } } else { entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, presentationData.strings.Channel_NotificationLoading)) @@ -603,7 +603,7 @@ private func privacyAndSecurityControllerEntries( entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, presentationData.strings.Channel_NotificationLoading)) entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, presentationData.strings.Channel_NotificationLoading)) if !isPremiumDisabled { - entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading, !isPremium)) + entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading, isPremium)) } //entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp)) @@ -933,7 +933,6 @@ public func privacyAndSecurityController( } })) }, openVoiceMessagePrivacy: { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } let signal = combineLatest( context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), privacySettingsPromise.get() @@ -941,42 +940,22 @@ public func privacyAndSecurityController( |> take(1) |> deliverOnMainQueue currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] peer, info in - let isPremium = peer?.isPremium ?? false - - if isPremium { - if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _, _ in - if let currentInfoDisposable = currentInfoDisposable { - let applySetting: Signal = privacySettingsPromise.get() - |> filter { $0 != nil } - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { value -> Signal in - if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) - } - return .complete() - } - currentInfoDisposable.set(applySetting.start()) - } - }), true) - } - } else { - let hapticFeedback = HapticFeedback() - hapticFeedback.impact() - - var alreadyPresented = false - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in - if action == .info { - if !alreadyPresented { - let controller = PremiumIntroScreen(context: context, source: .settings) - pushControllerImpl?(controller, true) - alreadyPresented = true + if let info = info { + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _, _ in + if let currentInfoDisposable = currentInfoDisposable { + let applySetting: Signal = privacySettingsPromise.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { value -> Signal in + if let value = value { + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + } + return .complete() } - return true + currentInfoDisposable.set(applySetting.start()) } - return false - })) + }), true) } })) }, openBioPrivacy: { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index c0a510b8e7e..f0dba91eb58 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -59,6 +59,7 @@ private final class SelectivePrivacySettingsControllerArguments { let removePublicPhoto: (() -> Void)? let updateHideReadTime: ((Bool) -> Void)? let openPremiumIntro: () -> Void + let displayLockedInfo: () -> Void init( context: AccountContext, @@ -71,7 +72,8 @@ private final class SelectivePrivacySettingsControllerArguments { setPublicPhoto: (() -> Void)?, removePublicPhoto: (() -> Void)?, updateHideReadTime: ((Bool) -> Void)?, - openPremiumIntro: @escaping () -> Void + openPremiumIntro: @escaping () -> Void, + displayLockedInfo: @escaping () -> Void ) { self.context = context self.updateType = updateType @@ -84,6 +86,7 @@ private final class SelectivePrivacySettingsControllerArguments { self.removePublicPhoto = removePublicPhoto self.updateHideReadTime = updateHideReadTime self.openPremiumIntro = openPremiumIntro + self.displayLockedInfo = displayLockedInfo } } @@ -116,9 +119,9 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case forwardsPreviewHeader(PresentationTheme, String) case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String) case settingHeader(PresentationTheme, String) - case everybody(PresentationTheme, String, Bool) - case contacts(PresentationTheme, String, Bool) - case nobody(PresentationTheme, String, Bool) + case everybody(PresentationTheme, String, Bool, Bool) + case contacts(PresentationTheme, String, Bool, Bool) + case nobody(PresentationTheme, String, Bool, Bool) case settingInfo(PresentationTheme, String, String) case exceptionsHeader(PresentationTheme, String) case disableFor(PresentationTheme, String, String) @@ -260,20 +263,20 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } else { return false } - case let .everybody(lhsTheme, lhsText, lhsValue): - if case let .everybody(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + case let .everybody(lhsTheme, lhsText, lhsValue, lhsIsLocked): + if case let .everybody(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked { return true } else { return false } - case let .contacts(lhsTheme, lhsText, lhsValue): - if case let .contacts(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + case let .contacts(lhsTheme, lhsText, lhsValue, lhsIsLocked): + if case let .contacts(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked { return true } else { return false } - case let .nobody(lhsTheme, lhsText, lhsValue): - if case let .nobody(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + case let .nobody(lhsTheme, lhsText, lhsValue, lhsIsLocked): + if case let .nobody(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked { return true } else { return false @@ -450,17 +453,28 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText) case let .settingHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) - case let .everybody(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { - arguments.updateType(.everybody) + case let .everybody(_, text, value, isLocked): + return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, zeroSeparatorInsets: false, sectionId: self.section, action: { + if isLocked { + } else { + arguments.updateType(.everybody) + } }) - case let .contacts(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { - arguments.updateType(.contacts) + case let .contacts(_, text, value, isLocked): + return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, zeroSeparatorInsets: false, sectionId: self.section, action: { + if isLocked { + arguments.displayLockedInfo() + } else { + arguments.updateType(.contacts) + } }) - case let .nobody(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { - arguments.updateType(.nobody) + case let .nobody(_, text, value, isLocked): + return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, zeroSeparatorInsets: false, sectionId: self.section, action: { + if isLocked { + arguments.displayLockedInfo() + } else { + arguments.updateType(.nobody) + } }) case let .settingInfo(_, text, link): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in @@ -696,10 +710,16 @@ private struct SelectivePrivacySettingsControllerState: Equatable { private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String, phoneNumber: String, peer: EnginePeer?, publicPhoto: TelegramMediaImage?) -> [SelectivePrivacySettingsEntry] { var entries: [SelectivePrivacySettingsEntry] = [] + let isPremium = peer?.isPremium ?? false + let settingTitle: String let settingInfoText: String? let disableForText: String let enableForText: String + + let phoneLink = "https://t.me/+\(phoneNumber)" + var settingInfoLink = phoneLink + switch kind { case .presence: settingTitle = presentationData.strings.PrivacyLastSeenSettings_WhoCanSeeMyTimestamp @@ -737,7 +757,12 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present enableForText = presentationData.strings.PrivacyLastSeenSettings_AlwaysShareWith case .voiceMessages: settingTitle = presentationData.strings.Privacy_VoiceMessages_WhoCanSend - settingInfoText = presentationData.strings.Privacy_VoiceMessages_CustomHelp + if isPremium { + settingInfoText = presentationData.strings.Privacy_VoiceMessages_CustomHelp + } else { + settingInfoText = presentationData.strings.Privacy_VoiceMessages_NonPremiumHelp + settingInfoLink = "premium" + } disableForText = presentationData.strings.Privacy_GroupsAndChannels_NeverAllow enableForText = presentationData.strings.Privacy_GroupsAndChannels_AlwaysAllow case .bio: @@ -767,12 +792,18 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.settingHeader(presentationData.theme, settingTitle)) - entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody)) - entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts)) - entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody)) - let phoneLink = "https://t.me/+\(phoneNumber)" + if case .voiceMessages = kind { + entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody || !isPremium, false)) + entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts && isPremium, !isPremium)) + entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody && isPremium, !isPremium)) + } else { + entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody, false)) + entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts, false)) + entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody, false)) + } + if let settingInfoText = settingInfoText { - entries.append(.settingInfo(presentationData.theme, settingInfoText, phoneLink)) + entries.append(.settingInfo(presentationData.theme, settingInfoText, settingInfoLink)) } if case .phoneNumber = kind, state.setting == .nobody { @@ -781,10 +812,13 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false)) entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink)) } + + if case .voiceMessages = kind, !isPremium { - entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) - - switch state.setting { + } else { + entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) + + switch state.setting { case .everybody: entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings))) case .contacts: @@ -792,21 +826,22 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings))) case .nobody: entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings))) - } - let exceptionsInfo: String - if case .profilePhoto = kind { - switch state.setting { - case .nobody: - exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideAddInfo - case .contacts: - exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideBothInfo - case .everybody: - exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideInfo } - } else { - exceptionsInfo = presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp + let exceptionsInfo: String + if case .profilePhoto = kind { + switch state.setting { + case .nobody: + exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideAddInfo + case .contacts: + exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideBothInfo + case .everybody: + exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideInfo + } + } else { + exceptionsInfo = presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp + } + entries.append(.peersInfo(presentationData.theme, exceptionsInfo)) } - entries.append(.peersInfo(presentationData.theme, exceptionsInfo)) if case .voiceCalls = kind, let p2pMode = state.callP2PMode, let integrationAvailable = state.callIntegrationAvailable, let integrationEnabled = state.callIntegrationEnabled { entries.append(.callsP2PHeader(presentationData.theme, presentationData.strings.Privacy_Calls_P2P.uppercased())) @@ -1148,10 +1183,15 @@ func selectivePrivacySettingsController( return state.withUpdatedPhoneDiscoveryEnabled(value) } }, copyPhoneLink: { link in - UIPasteboard.general.string = link - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + if link == "premium" { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) + pushControllerImpl?(controller, true) + } else { + UIPasteboard.general.string = link + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + } }, setPublicPhoto: { requestPublicPhotoSetup?({ result in var result = result @@ -1182,6 +1222,16 @@ func selectivePrivacySettingsController( }, openPremiumIntro: { let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) pushControllerImpl?(controller, true) + }, displayLockedInfo: { + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: presentationData.strings.Privacy_Messages_PremiumToast_Title, text: presentationData.strings.Privacy_Messages_PremiumToast_Text, customUndoText: presentationData.strings.Privacy_Messages_PremiumToast_Action, timeout: nil, linkAction: { _ in + }), elevatedLayout: false, action: { action in + if case .undo = action { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) + pushControllerImpl?(controller, true) + } + return false + }), nil) }) let peer = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index f9e693c8130..3d329a577ca 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -295,7 +295,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 29a2264c0de..4e664b9040a 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -442,7 +442,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index d5a0119b2c8..4864593b91d 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -475,6 +475,13 @@ public final class ShareController: ViewController { } } } + public var enqueued: (([PeerId], [Int64]) -> Void)? { + didSet { + if self.isNodeLoaded { + self.controllerNode.enqueued = enqueued + } + } + } public var openShareAsImage: (([Message]) -> Void)? @@ -741,6 +748,7 @@ public final class ShareController: ViewController { strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, fromForeignApp: self.fromForeignApp, forceTheme: self.forceTheme, fromPublicChannel: fromPublicChannel, segmentedValues: self.segmentedValues, shareStory: self.shareStory) self.controllerNode.completed = self.completed + self.controllerNode.enqueued = self.enqueued self.controllerNode.present = { [weak self] c in self?.presentInGlobalOverlay(c) } @@ -1873,7 +1881,7 @@ public final class ShareController: ViewController { case let .progress(value): return .progress(value) case .done: - return .done + return .done([]) } } } @@ -1908,21 +1916,21 @@ public final class ShareController: ViewController { } |> mapToSignal { progressSets -> Signal in if progressSets.isEmpty { - return .single(.done) + return .single(.done([])) } for item in progressSets { if case .progress = item { return .complete() } } - return .single(.done) + return .single(.done([])) } } } private func shareLegacy(text: String, peerIds: [EnginePeer.Id], topicIds: [EnginePeer.Id: Int64], showNames: Bool, silently: Bool) -> Signal { guard let currentContext = self.currentContext as? ShareControllerAppAccountContext else { - return .single(.done) + return .single(.done([])) } return currentContext.context.engine.data.get(EngineDataMap( peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)) @@ -1956,6 +1964,8 @@ public final class ShareController: ViewController { } } + var correlationIds: [Int64] = [] + switch subject { case let .url(url): for peerId in peerIds { @@ -2237,7 +2247,9 @@ public final class ShareController: ViewController { return .fail(.generic) } - messagesToEnqueue.append(.message(text: text, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + let correlationId = Int64.random(in: Int64.min ... Int64.max) + correlationIds.append(correlationId) + messagesToEnqueue.append(.message(text: text, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])) } for message in messages { for media in message.media { @@ -2295,7 +2307,9 @@ public final class ShareController: ViewController { } } - messagesToEnqueue.append(.forward(source: message.id, threadId: threadId, grouping: .auto, attributes: [], correlationId: nil)) + let correlationId = Int64.random(in: Int64.min ... Int64.max) + correlationIds.append(correlationId) + messagesToEnqueue.append(.forward(source: message.id, threadId: threadId, grouping: .auto, attributes: [], correlationId: correlationId)) } messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently) shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messagesToEnqueue)) @@ -2309,7 +2323,7 @@ public final class ShareController: ViewController { case let .progress(value): return .progress(value) case .done: - return .done + return .done([]) } } } @@ -2358,7 +2372,7 @@ public final class ShareController: ViewController { } } if !hasStatuses { - return .single(.done) + return .single(.done(correlationIds)) } return .complete() } diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index cec5c9ebd28..90c8435a2f5 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -13,7 +13,7 @@ import ContextUI enum ShareState { case preparing(Bool) case progress(Float) - case done + case done([Int64]) } enum ShareExternalState { @@ -67,6 +67,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate var debugAction: (() -> Void)? var openStats: (() -> Void)? var completed: (([PeerId]) -> Void)? + var enqueued: (([PeerId], [Int64]) -> Void)? var present: ((ViewController) -> Void)? var disabledPeerSelected: ((EnginePeer) -> Void)? @@ -954,7 +955,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate } } - if case .done = status, !fromForeignApp { + if case let .done(correlationIds) = status, !fromForeignApp { + strongSelf.enqueued?(peerIds, correlationIds) strongSelf.dismiss?(true) return } @@ -1456,7 +1458,8 @@ private func threadList(accountPeerId: EnginePeer.Id, postbox: Postbox, peerId: hasFailed: false, isContact: false, autoremoveTimeout: nil, - storyStats: nil + storyStats: nil, + displayAsTopicList: false )) } diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 403492a7e3b..3c7500dff88 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1438,7 +1438,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD } 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)) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil, forceOpenChat: true)) } } navigateToMessageImpl = { [weak controller] messageId in diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index a09ade3911c..059e3a9ee79 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -1851,14 +1851,15 @@ public extension Api.functions.auth { } } public extension Api.functions.auth { - static func signUp(phoneNumber: String, phoneCodeHash: String, firstName: String, lastName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func signUp(flags: Int32, phoneNumber: String, phoneCodeHash: String, firstName: String, lastName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-2131827673) + buffer.appendInt32(-1429752041) + serializeInt32(flags, buffer: buffer, boxed: false) serializeString(phoneNumber, buffer: buffer, boxed: false) serializeString(phoneCodeHash, buffer: buffer, boxed: false) serializeString(firstName, buffer: buffer, boxed: false) serializeString(lastName, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.signUp", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + return (FunctionDescription(name: "auth.signUp", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in let reader = BufferReader(buffer) var result: Api.auth.Authorization? if let signature = reader.readInt32() { @@ -5901,11 +5902,13 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func getSavedReactionTags(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getSavedReactionTags(flags: Int32, peer: Api.InputPeer?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1981668047) + buffer.appendInt32(909631579) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedReactionTags", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedReactionTags? in + return (FunctionDescription(name: "messages.getSavedReactionTags", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedReactionTags? in let reader = BufferReader(buffer) var result: Api.messages.SavedReactionTags? if let signature = reader.readInt32() { diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index 7b1431d82fe..0c9564781fd 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(speaker, _) = type, !speaker, let input = AVAudioSession.sharedInstance().availableInputs?.first { + if let dataSources = input.dataSources { + for source in dataSources { + if source.dataSourceName.contains("Front") { + try? input.setPreferredDataSource(source) + break + } + } + } + } + if resetToBuiltin { var updatedType = type if case .record(false, let withOthers) = updatedType, self.isHeadsetPluggedInValue { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index d8022be0764..49069c9aef6 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -29,7 +29,7 @@ public enum LocationBroadcastPanelSource { private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) { let presentImpl: (EngineMessage?) -> Void = { [weak controller] message in if let message = message, let strongController = controller { - let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { + let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { controller?.view.endEditing(true) }, present: { c, a in controller?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -65,6 +65,8 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { public private(set) var accessoryPanelContainerHeight: CGFloat = 0.0 public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility + public var tempHideAccessoryPanels: Bool = false + public let locationBroadcastPanelSource: LocationBroadcastPanelSource public let groupCallPanelSource: GroupCallPanelSource @@ -408,13 +410,17 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { let navigationHeight = super.navigationLayout(layout: layout).navigationFrame.height - self.additionalNavigationBarHeight let mediaAccessoryPanelHidden: Bool - switch self.mediaAccessoryPanelVisibility { - case .always: - mediaAccessoryPanelHidden = false - case .none: + if self.tempHideAccessoryPanels { mediaAccessoryPanelHidden = true - case let .specific(size): - mediaAccessoryPanelHidden = size != layout.metrics.widthClass + } else { + switch self.mediaAccessoryPanelVisibility { + case .always: + mediaAccessoryPanelHidden = false + case .none: + mediaAccessoryPanelHidden = true + case let .specific(size): + mediaAccessoryPanelHidden = size != layout.metrics.widthClass + } } var additionalHeight: CGFloat = 0.0 diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 6519c2dc1ba..b45da2ba3c3 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -291,6 +291,7 @@ private var declaredEncodables: Void = { declareEncodable(TelegramMediaGiveawayResults.self, f: { TelegramMediaGiveawayResults(decoder: $0) }) declareEncodable(WebpagePreviewMessageAttribute.self, f: { WebpagePreviewMessageAttribute(decoder: $0) }) declareEncodable(DerivedDataMessageAttribute.self, f: { DerivedDataMessageAttribute(decoder: $0) }) + declareEncodable(TelegramApplicationIcons.self, f: { TelegramApplicationIcons(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift index 290a0d02381..732fbe67272 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift @@ -9,7 +9,7 @@ public final class AdMessageAttribute: MessageAttribute { public enum MessageTarget { case peer(id: EnginePeer.Id, message: EngineMessage.Id?, startParam: String?) - case join(title: String, joinHash: String) + case join(title: String, joinHash: String, peer: EnginePeer?) case webPage(title: String, url: String) case botApp(peerId: EnginePeer.Id, app: BotApp, startParam: String?) } diff --git a/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift index 59d542d0790..59c86d13bf5 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift @@ -152,7 +152,10 @@ private func mergeReactions(reactions: [MessageReaction], recentPeers: [Reaction public func mergedMessageReactions(attributes: [MessageAttribute], isTags: Bool) -> ReactionsMessageAttribute? { // MARK: Nicegram HideReactions - guard !UserDefaults.standard.bool(forKey: "hideReactions") else { return nil } + if !isTags, + UserDefaults.standard.bool(forKey: "hideReactions") { + return nil + } // var current: ReactionsMessageAttribute? diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index e4f3a7f9e16..f6c36028a84 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -1223,10 +1223,14 @@ public enum SignUpError { case invalidLastName } -public func signUpWithName(accountManager: AccountManager, account: UnauthorizedAccount, firstName: String, lastName: String, avatarData: Data?, avatarVideo: Signal?, videoStartTimestamp: Double?, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?) -> Signal { +public func signUpWithName(accountManager: AccountManager, account: UnauthorizedAccount, firstName: String, lastName: String, avatarData: Data?, avatarVideo: Signal?, videoStartTimestamp: Double?, disableJoinNotifications: Bool = false, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let state = transaction.getState() as? UnauthorizedAccountState, case let .signUp(number, codeHash, _, _, _, syncContacts) = state.contents { - return account.network.request(Api.functions.auth.signUp(phoneNumber: number, phoneCodeHash: codeHash, firstName: firstName, lastName: lastName)) + var flags: Int32 = 0 + if disableJoinNotifications { + flags |= (1 << 0) + } + return account.network.request(Api.functions.auth.signUp(flags: flags, phoneNumber: number, phoneCodeHash: codeHash, firstName: firstName, lastName: lastName)) |> mapError { error -> SignUpError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index c0040a2c684..79351361b79 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -1136,7 +1136,8 @@ public func _internal_searchForumTopics(account: Account, peerId: EnginePeer.Id, hasFailed: false, isContact: false, autoremoveTimeout: nil, - storyStats: nil + storyStats: nil, + displayAsTopicList: false )) } diff --git a/submodules/TelegramCore/Sources/MacOS/MacInternalUpdater.swift b/submodules/TelegramCore/Sources/MacOS/MacInternal.swift similarity index 73% rename from submodules/TelegramCore/Sources/MacOS/MacInternalUpdater.swift rename to submodules/TelegramCore/Sources/MacOS/MacInternal.swift index afa55aabb56..897e6bb86c8 100644 --- a/submodules/TelegramCore/Sources/MacOS/MacInternalUpdater.swift +++ b/submodules/TelegramCore/Sources/MacOS/MacInternal.swift @@ -1,4 +1,3 @@ -#if os(macOS) import TelegramApi import SwiftSignalKit @@ -177,4 +176,73 @@ public func downloadAppUpdate(account: Account, source: String, messageId: Int32 } } -#endif +public func requestApplicationIcons(engine: TelegramEngine, source: String = "macos_app_icons") -> Signal { + return engine.peers.resolvePeerByName(name: source) + |> mapToSignal { result -> Signal in + switch result { + case .progress: + return .never() + case let .result(peer): + return .single(peer?._asPeer()) + } + } |> mapToSignal { peer -> Signal in + if let peer = peer, let inputPeer = apiInputPeer(peer) { + return engine.account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: 0, offsetDate: 0, addOffset: 0, limit: 100, maxId: Int32.max, minId: 0, hash: 0)) + |> retryRequest + |> mapToSignal { result in + + switch result { + case let .channelMessages(_, _, _, _, apiMessages, _, apiChats, apiUsers): + var icons: [TelegramApplicationIcons.Icon] = [] + for apiMessage in apiMessages.reversed() { + if let storeMessage = StoreMessage(apiMessage: apiMessage, accountPeerId: engine.account.peerId, peerIsForum: peer.isForum) { + var peers: [PeerId: Peer] = [:] + for chat in apiChats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers[groupOrChannel.id] = groupOrChannel + } + } + for user in apiUsers { + let telegramUser = TelegramUser(user: user) + peers[telegramUser.id] = telegramUser + } + + if let message = locallyRenderedMessage(message: storeMessage, peers: peers), let media = message.media.first as? TelegramMediaFile { + icons.append(.init(file: media, reference: MessageReference(message))) + } + } + } + + return _internal_updateApplicationIcons(postbox: engine.account.postbox, icons: .init(icons: icons)) + default: + break + } + return .complete() + } + } else { + return .complete() + } + } +} + + +/* + return Signal { subscriber in + let fetchDispsable = fetchedMediaResource(mediaBox: engine.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.media(media: AnyMediaReference.message(message: MessageReference(message), media: media), resource: media.resource)).start() + + let dataDisposable = engine.account.postbox.mediaBox.resourceData(media.resource, option: .complete(waitUntilFetchStatus: true)).start(next: { data in + if data.complete { + if let data = try? Data(contentsOf: URL.init(fileURLWithPath: data.path)) { + subscriber.putNext(data) + subscriber.putCompletion() + } else { + subscriber.putError(.xmlLoad) + } + } + }) + return ActionDisposable { + fetchDispsable.dispose() + dataDisposable.dispose() + } + } + */ diff --git a/submodules/TelegramCore/Sources/MacOS/TelegramApplicationIcons.swift b/submodules/TelegramCore/Sources/MacOS/TelegramApplicationIcons.swift new file mode 100644 index 00000000000..f86ff4f2bb9 --- /dev/null +++ b/submodules/TelegramCore/Sources/MacOS/TelegramApplicationIcons.swift @@ -0,0 +1,71 @@ +// +// File.swift +// +// +// Created by Mikhail Filimonov on 25.01.2024. +// + +import Foundation +import Postbox +import SwiftSignalKit + +public struct TelegramApplicationIcons : PostboxCoding, Equatable { + public init(decoder: PostboxDecoder) { + self.icons = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("i", decoder: { Icon(decoder: $0) })) ?? [] + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectArray(self.icons, forKey: "i") + } + + public struct Icon : PostboxCoding, Equatable { + public init(decoder: PostboxDecoder) { + self.file = decoder.decodeObjectForKey("f") as! TelegramMediaFile + self.reference = decoder.decodeObjectForKey("r", decoder: { MessageReference(decoder: $0) }) as! MessageReference + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.file, forKey: "f") + encoder.encodeObject(self.reference, forKey: "r") + } + + public let file: TelegramMediaFile + public let reference: MessageReference + init(file: TelegramMediaFile, reference: MessageReference) { + self.file = file + self.reference = reference + } + } + public var icons: [Icon] + + init(icons: [Icon]) { + self.icons = icons + } + + static var entryId: ItemCacheEntryId { + let cacheKey = ValueBoxKey(length: 1) + cacheKey.setInt8(0, value: 0) + return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.applicationIcons, key: cacheKey) + } +} + + +func _internal_applicationIcons(account: Account) -> Signal { + let key = PostboxViewKey.cachedItem(TelegramApplicationIcons.entryId) + return account.postbox.combinedView(keys: [key]) + |> mapToSignal { views -> Signal in + guard let icons = (views.views[key] as? CachedItemView)?.value?.getLegacy(TelegramApplicationIcons.self) as? TelegramApplicationIcons else { + return .single(.init(icons: [])) + } + return .single(icons) + } +} + +func _internal_updateApplicationIcons(postbox: Postbox, icons: TelegramApplicationIcons) -> Signal { + return postbox.transaction { transaction -> Void in + let entry = CodableEntry(legacyValue: icons) + transaction.putItemCacheEntry(id: TelegramApplicationIcons.entryId, entry: entry) + } +} + + diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 9ae1fa5e647..3ca88a16c7b 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -89,7 +89,6 @@ final class AccountTaskManager { tasks.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedApplyPendingScheduledMessagesActions(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start()) tasks.add(managedSynchronizeAvailableReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) - tasks.add(managedSynchronizeSavedMessageTags(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .emoji).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .status).start()) tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .avatar).start()) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 6b36ad4e3b7..defdf6d8c8e 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1732,11 +1732,13 @@ public final class AccountViewTracker { if context.subscribers.isEmpty { if let account = self.account { let queue = self.queue + Logger.shared.log("AccountViewTracker", "polledChannel: \(peerId) add keep polling") context.disposable.set(keepPollingChannel(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, peerId: peerId, stateManager: account.stateManager).start(next: { [weak context] isValidForTimeout in queue.async { guard let context = context else { return } + Logger.shared.log("AccountViewTracker", "polledChannel: \(peerId) set context isUpdated true for \(isValidForTimeout) seconds") context.isUpdated.set( .single(true) |> then( @@ -1756,6 +1758,7 @@ public final class AccountViewTracker { if let context = self.channelPollingContexts[peerId] { context.subscribers.remove(index) if context.subscribers.isEmpty { + Logger.shared.log("AccountViewTracker", "polledChannel: \(peerId) remove keep polling") context.disposable.set(nil) } } diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 3e08ccd1167..5ba4679264b 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -950,7 +950,15 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH case let .aroundId(aroundId): filledRange = min(aroundId.id, messageRange.lowerBound) ... max(aroundId.id, messageRange.upperBound) strictFilledIndices = IndexSet(integersIn: Int(min(aroundId.id, messageRange.lowerBound)) ... Int(max(aroundId.id, messageRange.upperBound))) + var shouldFillAround = false if peerInput.requestThreadId(accountPeerId: accountPeerId) != nil { + shouldFillAround = true + } + if case .customTag = space { + shouldFillAround = true + } + + if shouldFillAround { if ids.count <= count / 2 - 1 { filledRange = minMaxRange } diff --git a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift index 9289f647999..29623fb6b61 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift @@ -3,7 +3,39 @@ import Postbox import SwiftSignalKit import TelegramApi import MtProtoKit +import CryptoUtils +private struct Md5Hash: Hashable { + public let data: Data + + public init(data: Data) { + precondition(data.count == 16) + self.data = data + } +} + +private func md5Hash(_ data: Data) -> Md5Hash { + let hashData = data.withUnsafeBytes { bytes -> Data in + return CryptoMD5(bytes.baseAddress!, Int32(bytes.count)) + } + return Md5Hash(data: hashData) +} + +private func md5StringHash(_ string: String) -> UInt64 { + guard let data = string.data(using: .utf8) else { + return 0 + } + let hash = md5Hash(data).data + + return hash.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> UInt64 in + let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self) + var result: UInt64 = 0 + for i in 0 ... 7 { + result += UInt64(bitPattern: Int64(bytes[i])) << (56 - 8 * i) + } + return result + } +} private final class ManagedConsumePersonalMessagesActionsHelper { var operationDisposables: [MessageId: Disposable] = [:] @@ -461,17 +493,17 @@ func managedSynchronizeMessageHistoryTagSummaries(postbox: Postbox, network: Net for (entry, disposable) in beginValidateOperations { if entry.key.customTag != nil { if peerId == stateManager.accountPeerId { - let signal = synchronizeSavedMessageTags(postbox: postbox, network: network, peerId: peerId) + let signal = synchronizeSavedMessageTags(postbox: postbox, network: network, peerId: peerId, threadId: entry.key.threadId) |> map { _ -> Void in } |> then(postbox.transaction { transaction -> Void in - transaction.removeInvalidatedMessageHistoryTagsSummaryEntriesWithCustomTags(peerId: peerId, threadId: nil, namespace: Namespaces.Message.Cloud, tagMask: []) + transaction.removeInvalidatedMessageHistoryTagsSummaryEntriesWithCustomTags(peerId: peerId, threadId: entry.key.threadId, namespace: Namespaces.Message.Cloud, tagMask: []) }) disposable.set(signal.start()) } else { assertionFailure() let signal = postbox.transaction { transaction -> Void in - transaction.removeInvalidatedMessageHistoryTagsSummaryEntriesWithCustomTags(peerId: peerId, threadId: nil, namespace: Namespaces.Message.Cloud, tagMask: []) + transaction.removeInvalidatedMessageHistoryTagsSummaryEntriesWithCustomTags(peerId: peerId, threadId: entry.key.threadId, namespace: Namespaces.Message.Cloud, tagMask: []) } disposable.set(signal.start()) } @@ -529,61 +561,185 @@ private func synchronizeMessageHistoryTagSummary(postbox: Postbox, network: Netw |> switchToLatest } -private func synchronizeSavedMessageTags(postbox: Postbox, network: Network, peerId: PeerId) -> Signal { - return (network.request(Api.functions.messages.getSavedReactionTags(hash: 0)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) +func synchronizeSavedMessageTags(postbox: Postbox, network: Network, peerId: PeerId, threadId: Int64?) -> Signal { + 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 == peerId { + return false + } + } + + return true } - |> mapToSignal { result -> Signal in - guard let result = result else { - return .complete() + |> filter { $0 } + |> take(1) + |> ignoreValues + + let updateSignal: Signal = (postbox.transaction { transaction -> (Bool, Peer?, Int64) in + struct HashableTag { + var titleId: UInt64? + var count: Int + var id: UInt64 + + init(titleId: UInt64?, count: Int, id: UInt64) { + self.titleId = titleId + self.count = count + self.id = id + } } - switch result { - case .savedReactionTagsNotModified: - return .complete() - case let .savedReactionTags(tags, _): - 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 savedTags = _internal_savedMessageTags(transaction: transaction) + + var hashableTags: [HashableTag] = [] + for tag in transaction.getMessageTagSummaryCustomTags(peerId: peerId, threadId: threadId, tagMask: [], namespace: Namespaces.Message.Cloud) { + if let summary = transaction.getMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: [], namespace: Namespaces.Message.Cloud, customTag: tag), summary.count > 0 { + guard let reaction = ReactionsMessageAttribute.reactionFromMessageTag(tag: tag) else { + continue + } + + var tagTitle: String? + if threadId == nil, let savedTags { + if let value = savedTags.tags.first(where: { $0.reaction == reaction }) { + tagTitle = value.title } } - } - - let _ = customFileIds - - return postbox.transaction { transaction -> Void in - let previousTags = transaction.getMessageTagSummaryCustomTags(peerId: peerId, threadId: nil, tagMask: [], namespace: Namespaces.Message.Cloud) - let topMessageId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud)?.id ?? 1 + let reactionId: UInt64 + switch reaction { + case let .custom(id): + reactionId = UInt64(bitPattern: id) + case let .builtin(string): + reactionId = md5StringHash(string) + } - var validTags: [MemoryBuffer] = [] - for tag in parsedTags { - let customTag = ReactionsMessageAttribute.messageTag(reaction: tag.reaction) - validTags.append(customTag) - transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: [], namespace: Namespaces.Message.Cloud, customTag: customTag, count: Int32(tag.count), maxId: topMessageId) + var titleId: UInt64? + if let tagTitle { + titleId = md5StringHash(tagTitle) + } + + hashableTags.append(HashableTag( + titleId: titleId, + count: Int(summary.count), + id: reactionId + )) + } + } + + hashableTags.sort(by: { lhs, rhs in + if lhs.count != rhs.count { + return lhs.count > rhs.count + } + return lhs.id < rhs.id + }) + + var hashIds: [UInt64] = [] + for tag in hashableTags { + hashIds.append(tag.id) + if let titleId = tag.titleId { + hashIds.append(titleId) + } + hashIds.append(UInt64(tag.count)) + } + + var hashAcc: UInt64 = 0 + for id in hashIds { + combineInt64Hash(&hashAcc, with: id) + } + + return ( + transaction.getPreferencesEntry(key: PreferencesKeys.didCacheSavedMessageTags(threadId: threadId)) != nil, + threadId.flatMap { transaction.getPeer(PeerId($0)) }, + Int64(bitPattern: hashAcc) + ) + } + |> mapToSignal { alreadyCached, subPeer, currentHash -> Signal in + if alreadyCached { + return .complete() + } + + let inputSubPeer = subPeer.flatMap(apiInputPeer) + if threadId != nil && inputSubPeer == nil { + return .complete() + } + + var flags: Int32 = 0 + if inputSubPeer != nil { + flags |= 1 << 0 + } + + return network.request(Api.functions.messages.getSavedReactionTags(flags: flags, peer: inputSubPeer, hash: currentHash)) + |> 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 postbox.transaction { transaction -> Void in + transaction.setPreferencesEntry(key: PreferencesKeys.didCacheSavedMessageTags(threadId: threadId), value: PreferencesEntry(data: Data())) } - for tag in previousTags { - if !validTags.contains(tag) { - transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: [], namespace: Namespaces.Message.Cloud, customTag: tag, count: 0, maxId: topMessageId) + |> ignoreValues + case let .savedReactionTags(tags, _): + 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) + } + } + } + + return postbox.transaction { transaction -> Void in + if threadId == nil { + _internal_setSavedMessageTags(transaction: transaction, savedMessageTags: SavedMessageTags( + hash: 0, + tags: parsedTags + )) + } + + let previousTags = transaction.getMessageTagSummaryCustomTags(peerId: peerId, threadId: threadId, tagMask: [], namespace: Namespaces.Message.Cloud) + + let topMessageId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud)?.id ?? 1 + + var validTags: [MemoryBuffer] = [] + for tag in parsedTags { + let customTag = ReactionsMessageAttribute.messageTag(reaction: tag.reaction) + validTags.append(customTag) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: [], namespace: Namespaces.Message.Cloud, customTag: customTag, count: Int32(tag.count), maxId: topMessageId) } + for tag in previousTags { + if !validTags.contains(tag) { + transaction.replaceMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: [], namespace: Namespaces.Message.Cloud, customTag: tag, count: 0, maxId: topMessageId) + } + } + + transaction.setPreferencesEntry(key: PreferencesKeys.didCacheSavedMessageTags(threadId: threadId), value: PreferencesEntry(data: Data())) } + |> ignoreValues } - |> ignoreValues } }) + + return waitForApplySignal |> then(updateSignal |> delay(1.0, queue: .concurrentDefaultQueue())) } diff --git a/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift b/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift index e6ab49dae5b..efe3ecc81fd 100644 --- a/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift +++ b/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift @@ -157,8 +157,11 @@ private final class ManagedMessageHistoryHolesContext { }*/ for entry in entries { - if self.completedEntries[entry] != nil { - continue + if let previousTimestamp = self.completedEntries[entry] { + if previousTimestamp >= CFAbsoluteTimeGetCurrent() - 20.0 { + Logger.shared.log("ManagedMessageHistoryHoles", "Not adding recently finished entry \(entry)") + continue + } } switch entry.hole { diff --git a/submodules/TelegramCore/Sources/State/MessageReactions.swift b/submodules/TelegramCore/Sources/State/MessageReactions.swift index 607ef4c0659..372f736d971 100644 --- a/submodules/TelegramCore/Sources/State/MessageReactions.swift +++ b/submodules/TelegramCore/Sources/State/MessageReactions.swift @@ -18,10 +18,40 @@ public enum UpdateMessageReaction { } } -public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reactions: [UpdateMessageReaction], isLarge: Bool, storeAsRecentlyUsed: Bool) -> Signal { +public func updateMessageReactionsInteractively(account: Account, messageIds: [MessageId], reactions: [UpdateMessageReaction], isLarge: Bool, storeAsRecentlyUsed: Bool, add: Bool = false) -> Signal { return account.postbox.transaction { transaction -> Void in + guard let chatPeerId = messageIds.first?.peerId else { + return + } + + var messagesWithoutGroups: [Message] = [] + var messagesByGroupId: [Int64: [Message]] = [:] + + let messages = messageIds.compactMap { transaction.getMessage($0) } + for message in messages { + if let groupingKey = message.groupingKey { + if messagesByGroupId[groupingKey] == nil { + messagesByGroupId[groupingKey] = [message] + } else { + messagesByGroupId[groupingKey]?.append(message) + } + } else { + messagesWithoutGroups.append(message) + } + } + + var messageIds: [MessageId] = [] + for message in messagesWithoutGroups { + messageIds.append(message.id) + } + for (_, messages) in messagesByGroupId { + if let minMessageId = messages.map(\.id).min() { + messageIds.append(minMessageId) + } + } + var sendAsPeerId = account.peerId - if let cachedData = transaction.getPeerCachedData(peerId: messageId.peerId) { + if let cachedData = transaction.getPeerCachedData(peerId: chatPeerId) { if let cachedData = cachedData as? CachedChannelData { if let sendAsPeerIdValue = cachedData.sendAsPeerId { sendAsPeerId = sendAsPeerIdValue @@ -39,72 +69,96 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes maxCount = 1 } - var mappedReactions: [PendingReactionsMessageAttribute.PendingReaction] = [] - for reaction in reactions { - switch reaction { - case let .custom(fileId, file): - mappedReactions.append(PendingReactionsMessageAttribute.PendingReaction(value: .custom(fileId), sendAsPeerId: sendAsPeerId)) - if let file = file { - transaction.storeMediaIfNotPresent(media: file) + for messageId in messageIds { + var mappedReactions: [PendingReactionsMessageAttribute.PendingReaction] = [] + + var reactions: [UpdateMessageReaction] = reactions + if add { + if let message = transaction.getMessage(messageId), let effectiveReactions = message.effectiveReactions(isTags: message.areReactionsTags(accountPeerId: account.peerId)) { + for reaction in effectiveReactions { + if !reactions.contains(where: { $0.reaction == reaction.value }) { + let mappedValue: UpdateMessageReaction + switch reaction.value { + case let .builtin(value): + mappedValue = .builtin(value) + case let .custom(fileId): + mappedValue = .custom(fileId: fileId, file: nil) + } + reactions.append(mappedValue) + } + } } - case let .builtin(value): - mappedReactions.append(PendingReactionsMessageAttribute.PendingReaction(value: .builtin(value), sendAsPeerId: sendAsPeerId)) } - } - - transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction()) - transaction.updateMessage(messageId, update: { currentMessage in - var storeForwardInfo: StoreMessageForwardInfo? - if let forwardInfo = currentMessage.forwardInfo { - storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) + + for reaction in reactions { + switch reaction { + case let .custom(fileId, file): + mappedReactions.append(PendingReactionsMessageAttribute.PendingReaction(value: .custom(fileId), sendAsPeerId: sendAsPeerId)) + if let file = file { + transaction.storeMediaIfNotPresent(media: file) + } + case let .builtin(value): + mappedReactions.append(PendingReactionsMessageAttribute.PendingReaction(value: .builtin(value), sendAsPeerId: sendAsPeerId)) + } } - var attributes = currentMessage.attributes + + transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction()) + transaction.updateMessage(messageId, update: { currentMessage in + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) + } + var attributes = currentMessage.attributes loop: for j in 0 ..< attributes.count { if let _ = attributes[j] as? PendingReactionsMessageAttribute { attributes.remove(at: j) break loop } } - - if storeAsRecentlyUsed { - let effectiveReactions = currentMessage.effectiveReactions(isTags: currentMessage.areReactionsTags(accountPeerId: account.peerId)) ?? [] - for updatedReaction in reactions { - if !effectiveReactions.contains(where: { $0.value == updatedReaction.reaction && $0.isSelected }) { - let recentReactionItem: RecentReactionItem - switch updatedReaction { - case let .builtin(value): - recentReactionItem = RecentReactionItem(.builtin(value)) - case let .custom(fileId, file): - if let file = file ?? (transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile) { - recentReactionItem = RecentReactionItem(.custom(file)) - } else { - continue + + if storeAsRecentlyUsed { + let isTags = currentMessage.areReactionsTags(accountPeerId: account.peerId) + if !isTags { + let effectiveReactions = currentMessage.effectiveReactions(isTags: isTags) ?? [] + for updatedReaction in reactions { + if !effectiveReactions.contains(where: { $0.value == updatedReaction.reaction && $0.isSelected }) { + let recentReactionItem: RecentReactionItem + switch updatedReaction { + case let .builtin(value): + recentReactionItem = RecentReactionItem(.builtin(value)) + case let .custom(fileId, file): + if let file = file ?? (transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile) { + recentReactionItem = RecentReactionItem(.custom(file)) + } else { + continue + } + } + + if let entry = CodableEntry(recentReactionItem) { + let itemEntry = OrderedItemListEntry(id: recentReactionItem.id.rawValue, contents: entry) + transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentReactions, item: itemEntry, removeTailIfCountExceeds: 50) + } } } - - if let entry = CodableEntry(recentReactionItem) { - let itemEntry = OrderedItemListEntry(id: recentReactionItem.id.rawValue, contents: entry) - transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentReactions, item: itemEntry, removeTailIfCountExceeds: 50) - } } } - } - - var mappedReactions = mappedReactions - - let updatedReactions = mergedMessageReactions(attributes: attributes + [PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge, storeAsRecentlyUsed: storeAsRecentlyUsed, isTags: currentMessage.areReactionsTags(accountPeerId: account.peerId))], isTags: currentMessage.areReactionsTags(accountPeerId: account.peerId))?.reactions ?? [] - let updatedOutgoingReactions = updatedReactions.filter(\.isSelected) - if updatedOutgoingReactions.count > maxCount { - let sortedOutgoingReactions = updatedOutgoingReactions.sorted(by: { $0.chosenOrder! < $1.chosenOrder! }) - mappedReactions = Array(sortedOutgoingReactions.suffix(maxCount).map { reaction -> PendingReactionsMessageAttribute.PendingReaction in - return PendingReactionsMessageAttribute.PendingReaction(value: reaction.value, sendAsPeerId: sendAsPeerId) - }) - } - - attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge, storeAsRecentlyUsed: storeAsRecentlyUsed, isTags: currentMessage.areReactionsTags(accountPeerId: account.peerId))) - - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) - }) + + var mappedReactions = mappedReactions + + let updatedReactions = mergedMessageReactions(attributes: attributes + [PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge, storeAsRecentlyUsed: storeAsRecentlyUsed, isTags: currentMessage.areReactionsTags(accountPeerId: account.peerId))], isTags: currentMessage.areReactionsTags(accountPeerId: account.peerId))?.reactions ?? [] + let updatedOutgoingReactions = updatedReactions.filter(\.isSelected) + if updatedOutgoingReactions.count > maxCount { + let sortedOutgoingReactions = updatedOutgoingReactions.sorted(by: { $0.chosenOrder! < $1.chosenOrder! }) + mappedReactions = Array(sortedOutgoingReactions.suffix(maxCount).map { reaction -> PendingReactionsMessageAttribute.PendingReaction in + return PendingReactionsMessageAttribute.PendingReaction(value: reaction.value, sendAsPeerId: sendAsPeerId) + }) + } + + attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge, storeAsRecentlyUsed: storeAsRecentlyUsed, isTags: currentMessage.areReactionsTags(accountPeerId: account.peerId))) + + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } } |> ignoreValues } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 371e864c9d2..262224da8b1 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -149,6 +149,10 @@ final class PendingMessageRequestDependencyTag: NetworkRequestDependencyTag { } } +private final class CorrelationIdToSentMessageId { + var mapping: [Int64: MessageId] = [:] +} + public final class PendingMessageManager { private let network: Network private let postbox: Postbox @@ -174,6 +178,8 @@ public final class PendingMessageManager { var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? + private let correlationIdToSentMessageId: Atomic = Atomic(value: CorrelationIdToSentMessageId()) + init(network: Network, postbox: Postbox, accountPeerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, stateManager: AccountStateManager, localInputActivityManager: PeerInputActivityManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext) { Logger.shared.log("PendingMessageManager", "create instance") self.network = network @@ -1560,6 +1566,12 @@ public final class PendingMessageManager { var namespace = Namespaces.Message.Cloud if let apiMessage = apiMessage, let id = apiMessage.id(namespace: message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud) { namespace = id.namespace + + if let attribute = message.attributes.first(where: { $0 is OutgoingMessageInfoAttribute }) as? OutgoingMessageInfoAttribute, let correlationId = attribute.correlationId { + self.correlationIdToSentMessageId.with { value in + value.mapping[correlationId] = id + } + } } return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, cacheReferenceKey: content.cacheReferenceKey, result: result, accountPeerId: self.accountPeerId) @@ -1586,6 +1598,20 @@ public final class PendingMessageManager { } } + if messages.count == result.messages.count { + for i in 0 ..< messages.count { + let message = messages[i] + let apiMessage = result.messages[i] + if let id = apiMessage.id(namespace: message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud) { + if let attribute = message.attributes.first(where: { $0 is OutgoingMessageInfoAttribute }) as? OutgoingMessageInfoAttribute, let correlationId = attribute.correlationId { + self.correlationIdToSentMessageId.with { value in + value.mapping[correlationId] = id + } + } + } + } + } + return applyUpdateGroupMessages(postbox: postbox, stateManager: stateManager, messages: messages, result: result) |> afterDisposed { [weak self] in if let strongSelf = self { @@ -1665,4 +1691,8 @@ public final class PendingMessageManager { return disposable } } + + public func synchronouslyLookupCorrelationId(correlationId: Int64) -> MessageId? { + return self.correlationIdToSentMessageId.with { $0.mapping[correlationId] } + } } diff --git a/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift b/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift index c13048bf8cb..cf3a1a72b7e 100644 --- a/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift +++ b/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift @@ -3,22 +3,27 @@ import Postbox import TelegramApi internal func _internal_updateIsPremiumRequiredToContact(account: Account, peerIds: [EnginePeer.Id]) -> Signal<[EnginePeer.Id], NoError> { - return account.postbox.transaction { transaction -> ([Api.InputUser], [PeerId]) in + return account.postbox.transaction { transaction -> ([Api.InputUser], [PeerId], [PeerId]) in var inputUsers: [Api.InputUser] = [] - var premiumRequired:[EnginePeer.Id] = [] + let premiumRequired: [EnginePeer.Id] = [] + var ids:[PeerId] = [] for id in peerIds { if let peer = transaction.getPeer(id), let inputUser = apiInputUser(peer) { - if let cachedData = transaction.getPeerCachedData(peerId: id) as? CachedUserData { - if cachedData.flags.contains(.premiumRequired) { - premiumRequired.append(id) + if peer.isPremium { + if let cachedData = transaction.getPeerCachedData(peerId: id) as? CachedUserData { + if cachedData.flags.contains(.premiumRequired) { + inputUsers.append(inputUser) + ids.append(id) + } + } else if let peer = peer as? TelegramUser, peer.flags.contains(.requirePremium), !peer.flags.contains(.mutualContact) { + inputUsers.append(inputUser) + ids.append(id) } - } else { - inputUsers.append(inputUser) } } } - return (inputUsers, premiumRequired) - } |> mapToSignal { inputUsers, premiumRequired -> Signal<[EnginePeer.Id], NoError> in + return (inputUsers, premiumRequired, ids) + } |> mapToSignal { inputUsers, premiumRequired, reqIds -> Signal<[EnginePeer.Id], NoError> in if !inputUsers.isEmpty { return account.network.request(Api.functions.users.getIsPremiumRequiredToContact(id: inputUsers)) @@ -27,7 +32,7 @@ internal func _internal_updateIsPremiumRequiredToContact(account: Account, peerI return account.postbox.transaction { transaction in var requiredPeerIds: [EnginePeer.Id] = [] for (i, req) in result.enumerated() { - let peerId = peerIds[i] + let peerId = reqIds[i] let required = req == .boolTrue transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in let data = cachedData as? CachedUserData ?? CachedUserData() diff --git a/submodules/TelegramCore/Sources/State/SavedMessageTags.swift b/submodules/TelegramCore/Sources/State/SavedMessageTags.swift index c6172a75527..d7a232fc451 100644 --- a/submodules/TelegramCore/Sources/State/SavedMessageTags.swift +++ b/submodules/TelegramCore/Sources/State/SavedMessageTags.swift @@ -132,72 +132,26 @@ func _internal_setSavedMessageTags(transaction: Transaction, savedMessageTags: S } } -func managedSynchronizeSavedMessageTags(postbox: Postbox, network: Network) -> Signal { - let poll = Signal { subscriber in - let signal: Signal = _internal_savedMessageTags(postbox: postbox) - |> mapToSignal { current in - return (network.request(Api.functions.messages.getSavedReactionTags(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 - } - } - }) +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: []) + var updatedTags = value.tags + if let index = updatedTags.firstIndex(where: { $0.reaction == reaction }) { + updatedTags[index] = SavedMessageTags.Tag(reaction: updatedTags[index].reaction, title: title, count: updatedTags[index].count) + } else { + updatedTags.append(SavedMessageTags.Tag(reaction: reaction, title: title, count: 0)) } - - return signal.start(completed: { - subscriber.putCompletion() - }) + _internal_setSavedMessageTags(transaction: transaction, savedMessageTags: SavedMessageTags(hash: 0, tags: updatedTags)) + } + |> mapToSignal { _ -> Signal in + var flags: Int32 = 0 + if title != nil { + flags |= 1 << 0 + } + return account.network.request(Api.functions.messages.updateSavedReactionTag(flags: flags, reaction: reaction.apiReaction, title: title)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> ignoreValues } - - return ( - poll - |> then( - .complete() - |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()) - ) - ) - |> restart } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index b54867d1ff7..51506fd6daa 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 172 + return 173 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift index 060f3d50f6d..b088ef293d2 100644 --- a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift +++ b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift @@ -138,7 +138,7 @@ extension UserLimitsConfiguration { } self.maxPinnedChatCount = getValue("dialogs_pinned_limit", orElse: defaultValue.maxPinnedChatCount) - self.maxPinnedSavedChatCount = getValue("saved_pinned_limit", orElse: defaultValue.maxPinnedSavedChatCount) + self.maxPinnedSavedChatCount = getValue("saved_dialogs_pinned_limit", orElse: defaultValue.maxPinnedSavedChatCount) self.maxArchivedPinnedChatCount = getValue("dialogs_folder_pinned_limit", orElse: defaultValue.maxArchivedPinnedChatCount) self.maxChannelsCount = getValue("channels_limit", orElse: defaultValue.maxChannelsCount) self.maxPublicLinksCount = getValue("channels_public_limit", orElse: defaultValue.maxPublicLinksCount) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index c8abd9c147a..c61b94706a2 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -120,6 +120,7 @@ public struct Namespaces { public static let recommendedChannels: Int8 = 33 public static let peerColorOptions: Int8 = 34 public static let savedMessageTags: Int8 = 35 + public static let applicationIcons: Int8 = 36 } public struct UnorderedItemList { @@ -277,6 +278,8 @@ private enum PreferencesKeyValues: Int32 { case globalPrivacySettings = 31 case storiesConfiguration = 32 case audioTranscriptionTrialState = 33 + case didCacheSavedMessageTagsPrefix = 34 + case displaySavedChatsAsTopics = 35 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -447,6 +450,19 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.audioTranscriptionTrialState.rawValue) return key }() + + public static func didCacheSavedMessageTags(threadId: Int64?) -> ValueBoxKey { + let key = ValueBoxKey(length: 4 + 8) + key.setInt32(0, value: PreferencesKeyValues.didCacheSavedMessageTagsPrefix.rawValue) + key.setInt64(4, value: threadId ?? 0) + return key + } + + public static func displaySavedChatsAsTopics() -> ValueBoxKey { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.displaySavedChatsAsTopics.rawValue) + return key + } } private enum SharedDataKeyValues: Int32 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index 2932141570f..94bc0cd63de 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -235,7 +235,8 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { } return result - } + }, + displaySavedMessagesAsTopicListPreferencesKey: PreferencesKeys.displaySavedChatsAsTopics() ) }() diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift index 39c1df9b282..47ee37c16b8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift @@ -333,24 +333,27 @@ public extension TelegramEngine.EngineData.Item { public struct ReactionTagMessageCount: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public struct ItemKey: Hashable { public var peerId: EnginePeer.Id + public var threadId: Int64? public var reaction: MessageReaction.Reaction } public typealias Result = Int? fileprivate var peerId: EnginePeer.Id + fileprivate var threadId: Int64? fileprivate var reaction: MessageReaction.Reaction public var mapKey: ItemKey { - return ItemKey(peerId: self.peerId, reaction: self.reaction) + return ItemKey(peerId: self.peerId, threadId: self.threadId, reaction: self.reaction) } - public init(peerId: EnginePeer.Id, reaction: MessageReaction.Reaction) { + public init(peerId: EnginePeer.Id, threadId: Int64?, reaction: MessageReaction.Reaction) { self.peerId = peerId + self.threadId = threadId self.reaction = reaction } var key: PostboxViewKey { - return .historyTagSummaryView(tag: [], peerId: self.peerId, threadId: nil, namespace: Namespaces.Message.Cloud, customTag: ReactionsMessageAttribute.messageTag(reaction: self.reaction)) + return .historyTagSummaryView(tag: [], peerId: self.peerId, threadId: self.threadId, namespace: Namespaces.Message.Cloud, customTag: ReactionsMessageAttribute.messageTag(reaction: self.reaction)) } func extract(view: PostboxView) -> Result { @@ -392,13 +395,15 @@ public extension TelegramEngine.EngineData.Item { public typealias Result = [MessageReaction.Reaction: Int] fileprivate var peerId: EnginePeer.Id + fileprivate var threadId: Int64? - public init(peerId: EnginePeer.Id) { + public init(peerId: EnginePeer.Id, threadId: Int64?) { self.peerId = peerId + self.threadId = threadId } var key: PostboxViewKey { - return .historyCustomTagSummariesView(peerId: self.peerId, namespace: Namespaces.Message.Cloud) + return .historyCustomTagSummariesView(peerId: self.peerId, threadId: self.threadId, namespace: Namespaces.Message.Cloud) } func extract(view: PostboxView) -> Result { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index d24b4d18401..9b3049aac8e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -18,6 +18,14 @@ public enum EnginePeerCachedInfoItem { } } +public struct EngineDisplaySavedChatsAsTopics: Codable, Equatable { + public var value: Bool + + public init(value: Bool) { + self.value = value + } +} + extension EnginePeerCachedInfoItem: Equatable where T: Equatable { public static func ==(lhs: EnginePeerCachedInfoItem, rhs: EnginePeerCachedInfoItem) -> Bool { switch lhs { @@ -1245,5 +1253,28 @@ public extension TelegramEngine.EngineData.Item { } } + public struct DisplaySavedChatsAsTopics: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = Bool + + public init() { + } + + var key: PostboxViewKey { + return .preferences(keys: Set([PreferencesKeys.displaySavedChatsAsTopics()])) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? PreferencesView else { + preconditionFailure() + } + + if let value = view.values[PreferencesKeys.displaySavedChatsAsTopics()]?.get(EngineDisplaySavedChatsAsTopics.self) { + return value.value + } else { + return false + } + } + } + } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index 3521688df23..e16c15a59a1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -42,16 +42,41 @@ private class AdMessagesHistoryContextImpl { case title case joinHash case nameColor + case image + case peer } var title: String var joinHash: String var nameColor: PeerNameColor? + var image: TelegramMediaImage? + var peer: Peer? - init(title: String, joinHash: String, nameColor: PeerNameColor? = nil) { + init(title: String, joinHash: String, nameColor: PeerNameColor?, image: TelegramMediaImage?, peer: Peer?) { self.title = title self.joinHash = joinHash self.nameColor = nameColor + self.image = image + self.peer = peer + } + + static func ==(lhs: Invite, rhs: Invite) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.joinHash != rhs.joinHash { + return false + } + if lhs.nameColor != rhs.nameColor { + return false + } + if lhs.image != rhs.image { + return false + } + if !arePeersEqual(lhs.peer, rhs.peer) { + return false + } + return true } init(from decoder: Decoder) throws { @@ -60,6 +85,12 @@ private class AdMessagesHistoryContextImpl { self.title = try container.decode(String.self, forKey: .title) self.joinHash = try container.decode(String.self, forKey: .joinHash) self.nameColor = try container.decodeIfPresent(Int32.self, forKey: .nameColor).flatMap { PeerNameColor(rawValue: $0) } + self.image = (try container.decodeIfPresent(Data.self, forKey: .image)).flatMap { data in + return TelegramMediaImage(decoder: PostboxDecoder(buffer: MemoryBuffer(data: data))) + } + self.peer = (try container.decodeIfPresent(Data.self, forKey: .peer)).flatMap { data in + return PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? Peer + } } func encode(to encoder: Encoder) throws { @@ -68,6 +99,16 @@ private class AdMessagesHistoryContextImpl { try container.encode(self.title, forKey: .title) try container.encode(self.joinHash, forKey: .joinHash) try container.encodeIfPresent(self.nameColor?.rawValue, forKey: .nameColor) + try container.encodeIfPresent(self.image.flatMap { image in + let encoder = PostboxEncoder() + image.encode(encoder) + return encoder.makeData() + }, forKey: .image) + try container.encodeIfPresent(self.peer.flatMap { peer in + let encoder = PostboxEncoder() + encoder.encodeRootObject(peer) + return encoder.makeData() + }, forKey: .peer) } } @@ -261,7 +302,7 @@ private class AdMessagesHistoryContextImpl { case let .peer(peerId): target = .peer(id: peerId, message: self.messageId, startParam: self.startParam) case let .invite(invite): - target = .join(title: invite.title, joinHash: invite.joinHash) + target = .join(title: invite.title, joinHash: invite.joinHash, peer: invite.peer.flatMap(EnginePeer.init)) case let .webPage(webPage): target = .webPage(title: webPage.title, url: webPage.url) case let .botApp(peerId, botApp): @@ -350,6 +391,11 @@ private class AdMessagesHistoryContextImpl { let messageHash = (self.text.hashValue &+ 31 &* peerId.hashValue) &* 31 &+ author.id.hashValue let messageStableVersion = UInt32(bitPattern: Int32(truncatingIfNeeded: messageHash)) + + var media: [Media] = self.media + if media.isEmpty, case let .invite(invite) = self.target, let image = invite.image { + media.append(image) + } return Message( stableId: 0, @@ -369,7 +415,7 @@ private class AdMessagesHistoryContextImpl { author: author, text: self.text, attributes: attributes, - media: self.media, + media: media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], @@ -557,7 +603,7 @@ private class AdMessagesHistoryContextImpl { } let isRecommended = (flags & (1 << 5)) != 0 - let displayAvatar = (flags & (1 << 6)) != 0 + var displayAvatar = (flags & (1 << 6)) != 0 var target: CachedMessage.Target? if let fromId = fromId { @@ -575,10 +621,9 @@ private class AdMessagesHistoryContextImpl { } else if let chatInvite = chatInvite, let chatInviteHash = chatInviteHash { switch chatInvite { case let .chatInvite(flags, title, _, photo, participantsCount, participants, nameColor): - let photo = telegramMediaImageFromApiPhoto(photo).flatMap({ smallestImageRepresentation($0.representations) }) + let image = telegramMediaImageFromApiPhoto(photo) let flags: ExternalJoiningChatState.Invite.Flags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0, isVerified: (flags & (1 << 7)) != 0, isScam: (flags & (1 << 8)) != 0, isFake: (flags & (1 << 9)) != 0) - let _ = photo let _ = flags let _ = participantsCount let _ = participants @@ -586,24 +631,36 @@ private class AdMessagesHistoryContextImpl { target = .invite(CachedMessage.Target.Invite( title: title, joinHash: chatInviteHash, - nameColor: PeerNameColor(rawValue: nameColor) + nameColor: PeerNameColor(rawValue: nameColor), + image: displayAvatar ? image : nil, + peer: nil )) + + displayAvatar = false case let .chatInvitePeek(chat, _): if let peer = parseTelegramGroupOrChannel(chat: chat) { target = .invite(CachedMessage.Target.Invite( title: peer.debugDisplayTitle, joinHash: chatInviteHash, - nameColor: peer.nameColor + nameColor: peer.nameColor, + image: nil, + peer: displayAvatar ? peer : nil )) } + + displayAvatar = false case let .chatInviteAlready(chat): if let peer = parseTelegramGroupOrChannel(chat: chat) { target = .invite(CachedMessage.Target.Invite( title: peer.debugDisplayTitle, joinHash: chatInviteHash, - nameColor: peer.nameColor + nameColor: peer.nameColor, + image: nil, + peer: displayAvatar ? peer : nil )) } + + displayAvatar = false } } // else if let botApp = app.flatMap({ BotApp(apiBotApp: $0) }) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index 6a765d40c73..a0544481eee 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -129,6 +129,7 @@ public final class EngineChatList: Equatable { public let isContact: Bool public let autoremoveTimeout: Int32? public let storyStats: StoryStats? + public let displayAsTopicList: Bool public init( id: Id, @@ -147,7 +148,8 @@ public final class EngineChatList: Equatable { hasFailed: Bool, isContact: Bool, autoremoveTimeout: Int32?, - storyStats: StoryStats? + storyStats: StoryStats?, + displayAsTopicList: Bool ) { self.id = id self.index = index @@ -166,6 +168,7 @@ public final class EngineChatList: Equatable { self.isContact = isContact self.autoremoveTimeout = autoremoveTimeout self.storyStats = storyStats + self.displayAsTopicList = displayAsTopicList } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -220,6 +223,9 @@ public final class EngineChatList: Equatable { if lhs.storyStats != rhs.storyStats { return false } + if lhs.displayAsTopicList != rhs.displayAsTopicList { + return false + } return true } } @@ -419,7 +425,7 @@ public extension EngineChatList.RelativePosition { } extension EngineChatList.Item { - convenience init?(_ entry: ChatListEntry) { + convenience init?(_ entry: ChatListEntry, displayAsTopicList: Bool) { switch entry { case let .MessageEntry(entryData): let index = entryData.index @@ -504,7 +510,8 @@ extension EngineChatList.Item { hasFailed: hasFailed, isContact: isContact, autoremoveTimeout: autoremoveTimeout, - storyStats: entryData.storyStats + storyStats: entryData.storyStats, + displayAsTopicList: displayAsTopicList ) case .HoleEntry: return nil @@ -544,7 +551,7 @@ extension EngineChatList.AdditionalItem.PromoInfo { extension EngineChatList.AdditionalItem { convenience init?(_ entry: ChatListAdditionalItemEntry) { - guard let item = EngineChatList.Item(entry.entry) else { + guard let item = EngineChatList.Item(entry.entry, displayAsTopicList: false) else { return nil } guard let promoInfo = (entry.info as? PromoChatListItem).flatMap(EngineChatList.AdditionalItem.PromoInfo.init) else { @@ -555,14 +562,19 @@ extension EngineChatList.AdditionalItem { } public extension EngineChatList { - convenience init(_ view: ChatListView) { + convenience init(_ view: ChatListView, accountPeerId: PeerId) { var isLoading = false + + var displaySavedMessagesAsTopicList = false + if let value = view.displaySavedMessagesAsTopicList?.get(EngineDisplaySavedChatsAsTopics.self) { + displaySavedMessagesAsTopicList = value.value + } var items: [EngineChatList.Item] = [] loop: for entry in view.entries { switch entry { case .MessageEntry: - if let item = EngineChatList.Item(entry) { + if let item = EngineChatList.Item(entry, displayAsTopicList: entry.index.messageIndex.id.peerId == accountPeerId ? displaySavedMessagesAsTopicList : false) { items.append(item) } case .HoleEntry: diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift index b6493a342bb..6b8e7940241 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift @@ -2,6 +2,7 @@ import Postbox public final class EngineMessage: Equatable { public typealias Id = MessageId + public typealias StableId = UInt32 public typealias Index = MessageIndex public typealias Tags = MessageTags public typealias Attribute = MessageAttribute diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index 9ed290d7a3b..6917c3cb732 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -42,9 +42,9 @@ private struct SearchMessagesPeerState: Equatable { } } -public struct SearchMessagesResult { +public struct SearchMessagesResult: Equatable { public let messages: [Message] - public let threadInfo:[MessageId : MessageHistoryThreadData] + public let threadInfo: [MessageId: MessageHistoryThreadData] public let readStates: [PeerId: CombinedPeerReadState] public let totalCount: Int32 public let completed: Bool @@ -56,6 +56,21 @@ public struct SearchMessagesResult { self.totalCount = totalCount self.completed = completed } + + public static func ==(lhs: SearchMessagesResult, rhs: SearchMessagesResult) -> Bool { + if lhs.messages.count != rhs.messages.count { + return false + } + for i in 0 ..< lhs.messages.count { + if lhs.messages[i].index != rhs.messages[i].index { + return false + } + if lhs.messages[i].stableVersion != rhs.messages[i].stableVersion { + return false + } + } + return true + } } public struct SearchMessagesState: Equatable { @@ -222,6 +237,11 @@ private func mergedResult(_ state: SearchMessagesState) -> SearchMessagesResult } func _internal_searchMessages(account: Account, location: SearchMessagesLocation, query: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> { + if case let .peer(peerId, fromId, tags, reactions, threadId, minDate, maxDate) = location, fromId == nil, tags == nil, let reactions, !reactions.isEmpty, threadId == nil, minDate == nil, maxDate == 0 { + let _ = peerId + print("short cirquit") + } + let remoteSearchResult: Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> switch location { case let .peer(peerId, fromId, tags, reactions, threadId, minDate, maxDate): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift index 7d86fb359c0..5f39622ea4e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift @@ -726,6 +726,7 @@ public final class SparseMessageCalendar { private let peerId: PeerId private let threadId: Int64? private let messageTag: MessageTags + private let displayMedia: Bool private var state: InternalState let statePromise = Promise() @@ -742,12 +743,13 @@ public final class SparseMessageCalendar { return self.isLoadingMorePromise.get() } - init(queue: Queue, account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags) { + init(queue: Queue, account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags, displayMedia: Bool) { self.queue = queue self.account = account self.peerId = peerId self.threadId = threadId self.messageTag = messageTag + self.displayMedia = displayMedia self.state = InternalState(nextRequestOffset: 0, minTimestamp: nil, messagesByDay: [:]) self.statePromise.set(.single(self.state)) @@ -791,6 +793,9 @@ public final class SparseMessageCalendar { if self.threadId != nil { return } + if !self.displayMedia { + return + } self.isLoadingMore = true @@ -905,11 +910,11 @@ public final class SparseMessageCalendar { public var minTimestamp: Int32? private var disposable: Disposable? - init(account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags) { + init(account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags, displayMedia: Bool) { let queue = Queue() self.queue = queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, account: account, peerId: peerId, threadId: threadId, messageTag: messageTag) + return Impl(queue: queue, account: account, peerId: peerId, threadId: threadId, messageTag: messageTag, displayMedia: displayMedia) }) self.disposable = self.state.start(next: { [weak self] state in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 99a9c0fcafb..bbd887a0b00 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -18,7 +18,6 @@ public final class StoryPreloadInfo { public let peer: PeerReference public let storyId: Int32 public let media: EngineMedia - public let alternativeMedia: EngineMedia? public let reactions: [MessageReaction.Reaction] public let priority: Priority @@ -26,14 +25,12 @@ public final class StoryPreloadInfo { peer: PeerReference, storyId: Int32, media: EngineMedia, - alternativeMedia: EngineMedia?, reactions: [MessageReaction.Reaction], priority: Priority ) { self.peer = peer self.storyId = storyId self.media = media - self.alternativeMedia = alternativeMedia self.reactions = reactions self.priority = priority } @@ -316,15 +313,30 @@ public extension TelegramEngine { } public func setMessageReactions( - id: EngineMessage.Id, + ids: [EngineMessage.Id], reactions: [UpdateMessageReaction] ) { let _ = updateMessageReactionsInteractively( account: self.account, - messageId: id, + messageIds: ids, reactions: reactions, isLarge: false, - storeAsRecentlyUsed: false + storeAsRecentlyUsed: false, + add: false + ).start() + } + + public func addMessageReactions( + ids: [EngineMessage.Id], + reactions: [UpdateMessageReaction] + ) { + let _ = updateMessageReactionsInteractively( + account: self.account, + messageIds: ids, + reactions: reactions, + isLarge: false, + storeAsRecentlyUsed: false, + add: true ).start() } @@ -345,9 +357,10 @@ public extension TelegramEngine { } public func chatList(group: EngineChatList.Group, count: Int) -> Signal { + let accountPeerId = self.account.peerId return self.account.postbox.tailChatListView(groupId: group._asGroup(), count: count, summaryComponents: ChatListEntrySummaryComponents()) |> map { view -> EngineChatList in - return EngineChatList(view.0) + return EngineChatList(view.0, accountPeerId: accountPeerId) } } @@ -419,14 +432,10 @@ public extension TelegramEngine { return SparseMessageList(account: self.account, peerId: peerId, threadId: threadId, messageTag: tag) } - public func sparseMessageCalendar(peerId: EnginePeer.Id, threadId: Int64?, tag: EngineMessage.Tags) -> SparseMessageCalendar { - return SparseMessageCalendar(account: self.account, peerId: peerId, threadId: threadId, messageTag: tag) + public func sparseMessageCalendar(peerId: EnginePeer.Id, threadId: Int64?, tag: EngineMessage.Tags, displayMedia: Bool) -> SparseMessageCalendar { + return SparseMessageCalendar(account: self.account, peerId: peerId, threadId: threadId, messageTag: tag, displayMedia: displayMedia) } - /*public func sparseMessageScrollingContext(peerId: EnginePeer.Id) -> SparseMessageScrollingContext { - return SparseMessageScrollingContext(account: self.account, peerId: peerId) - }*/ - public func refreshMessageTagStats(peerId: EnginePeer.Id, threadId: Int64?, tags: [EngineMessage.Tags]) -> Signal { let account = self.account return self.account.postbox.transaction { transaction -> (Api.InputPeer?, Api.InputPeer?) in @@ -1076,16 +1085,19 @@ public extension TelegramEngine { } } - public func preloadStorySubscriptions(isHidden: Bool) -> Signal<[EngineMedia.Id: StoryPreloadInfo], NoError> { + public func preloadStorySubscriptions(isHidden: Bool, preferHighQuality: Signal) -> Signal<[EngineMedia.Id: StoryPreloadInfo], NoError> { let basicPeerKey = PostboxViewKey.basicPeer(self.account.peerId) let subscriptionsKey: PostboxStorySubscriptionsKey = isHidden ? .hidden : .filtered let storySubscriptionsKey = PostboxViewKey.storySubscriptions(key: subscriptionsKey) - return self.account.postbox.combinedView(keys: [ - basicPeerKey, - storySubscriptionsKey, - PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey)) - ]) - |> mapToSignal { views -> Signal<[EngineMedia.Id: StoryPreloadInfo], NoError> in + return combineLatest( + self.account.postbox.combinedView(keys: [ + basicPeerKey, + storySubscriptionsKey, + PostboxViewKey.storiesState(key: .subscriptions(subscriptionsKey)) + ]), + preferHighQuality + ) + |> mapToSignal { views, preferHighQuality -> Signal<[EngineMedia.Id: StoryPreloadInfo], NoError> in guard let basicPeerView = views.views[basicPeerKey] as? BasicPeerView, let accountPeer = basicPeerView.peer else { return .single([:]) } @@ -1192,11 +1204,17 @@ public extension TelegramEngine { } } + var selectedMedia: EngineMedia + if let alternativeMedia = itemAndPeer.item.alternativeMedia.flatMap(EngineMedia.init), !preferHighQuality { + selectedMedia = alternativeMedia + } else { + selectedMedia = EngineMedia(media) + } + resultResources[mediaId] = StoryPreloadInfo( peer: peerReference, storyId: itemAndPeer.item.id, - media: EngineMedia(media), - alternativeMedia: itemAndPeer.item.alternativeMedia.flatMap(EngineMedia.init), + media: selectedMedia, reactions: reactions, priority: .top(position: nextPriority) ) @@ -1340,6 +1358,10 @@ public extension TelegramEngine { return self.account.stateManager.synchronouslyIsMessageDeletedInteractively(ids: ids) } + public func synchronouslyLookupCorrelationId(correlationId: Int64) -> EngineMessage.Id? { + return self.account.pendingMessageManager.synchronouslyLookupCorrelationId(correlationId: correlationId) + } + public func savedMessagesPeerListHead() -> Signal { return self.account.postbox.combinedView(keys: [.savedMessagesIndex(peerId: self.account.peerId)]) |> map { views -> EnginePeer.Id? in @@ -1355,6 +1377,21 @@ public extension TelegramEngine { } } + public func savedMessagesHasPeersOtherThanSaved() -> Signal { + return self.account.postbox.combinedView(keys: [.savedMessagesIndex(peerId: self.account.peerId)]) + |> map { views -> Bool in + //TODO:api optimize + guard let view = views.views[.savedMessagesIndex(peerId: self.account.peerId)] as? MessageHistorySavedMessagesIndexView else { + return false + } + if view.isLoading { + return false + } else { + return view.items.contains(where: { $0.peer?.id != self.account.peerId }) + } + } + } + public func savedMessagesPeersStats() -> Signal { return self.account.postbox.combinedView(keys: [.savedMessagesStats(peerId: self.account.peerId)]) |> map { views -> Int? in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index beed309046c..eecb8dc9965 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1064,13 +1064,13 @@ public extension TelegramEngine { public func toggleForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64) -> Signal { return self.account.postbox.transaction { transaction -> ([Int64], Int) in if id == self.account.peerId { - var limit = 5 let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue - if let data = appConfiguration.data, let value = data["saved_pinned_limit"] as? Double { - limit = Int(value) - } - return (transaction.getPeerPinnedThreads(peerId: id), limit) + let accountPeer = transaction.getPeer(self.account.peerId) + let limitsConfiguration = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: accountPeer?.isPremium ?? false) + let limit = limitsConfiguration.maxPinnedSavedChatCount + + return (transaction.getPeerPinnedThreads(peerId: id), Int(limit)) } else { var limit = 5 let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue @@ -1346,6 +1346,14 @@ public extension TelegramEngine { |> distinctUntilChanged } } + + public func updateSavedMessagesViewAsTopics(value: Bool) { + let _ = (self.account.postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: PreferencesKeys.displaySavedChatsAsTopics(), { _ in + return PreferencesEntry(EngineDisplaySavedChatsAsTopics(value: value)) + }) + }).start() + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index f49db12037e..ded57ff0d6f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -412,5 +412,9 @@ public extension TelegramEngine { public func pushPriorityDownload(resourceId: String, priority: Int = 1) -> Disposable { return self.account.network.multiplexedRequestManager.pushPriority(resourceId: resourceId, priority: priority) } + + public func applicationIcons() -> Signal { + return _internal_applicationIcons(account: account) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index 7011acd75e9..e124cf46599 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -109,6 +109,19 @@ public extension TelegramEngine { return _internal_cachedAvailableReactions(postbox: self.account.postbox) } + public func savedMessageTagData() -> Signal { + return self.account.postbox.combinedView(keys: [PostboxViewKey.cachedItem(_internal_savedMessageTagsCacheKey())]) + |> mapToSignal { views -> Signal in + guard let views = views.views[PostboxViewKey.cachedItem(_internal_savedMessageTagsCacheKey())] as? CachedItemView else { + return .single(nil) + } + guard let savedMessageTags = views.value?.get(SavedMessageTags.self) else { + return .single(nil) + } + return .single(savedMessageTags) + } + } + public func savedMessageTags() -> Signal<([SavedMessageTags.Tag], [Int64: TelegramMediaFile]), NoError> { return self.account.postbox.combinedView(keys: [PostboxViewKey.cachedItem(_internal_savedMessageTagsCacheKey())]) |> mapToSignal { views -> Signal<([SavedMessageTags.Tag], [Int64: TelegramMediaFile]), NoError> in @@ -133,8 +146,12 @@ public extension TelegramEngine { } } - public func refreshSavedMessageTags() -> Signal { - return managedSynchronizeSavedMessageTags(postbox: self.account.postbox, network: self.account.network) + public func refreshSavedMessageTags(subPeerId: EnginePeer.Id?) -> Signal { + return synchronizeSavedMessageTags(postbox: self.account.postbox, network: self.account.network, peerId: self.account.peerId, threadId: subPeerId?.toInt64()) + } + + public func setSavedMessageTagTitle(reaction: MessageReaction.Reaction, title: String?) -> Signal { + return _internal_setSavedMessageTagTitle(account: self.account, reaction: reaction, title: title) } public func emojiSearchCategories(kind: EmojiSearchCategories.Kind) -> Signal { diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index ec50d045a41..22173d47cb6 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -489,13 +489,13 @@ public extension Message { public extension Message { func areReactionsTags(accountPeerId: PeerId) -> Bool { - /*if self.id.peerId == accountPeerId { + if self.id.peerId == accountPeerId { if let reactionsAttribute = self.reactionsAttribute, !reactionsAttribute.reactions.isEmpty { return reactionsAttribute.isTags } else { return true } - }*/ + } return false } } diff --git a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift index 703184b9801..e068920e0d3 100644 --- a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift @@ -1,6 +1,8 @@ import Foundation import Postbox +public let anonymousSavedMessagesId: Int64 = 2666000 + public extension Peer { var debugDisplayTitle: String { switch self { @@ -24,6 +26,21 @@ public extension Peer { let reason = restrictionReason(contentSettings: contentSettings) return reason?.contains("porn") ?? false } + + func unblockRequiresAnotherPhoneNumber() -> Bool { + let restrictionInfo: PeerAccessRestrictionInfo? = switch self { + case let user as TelegramUser: + user.restrictionInfo + case let channel as TelegramChannel: + channel.restrictionInfo + default: + nil + } + + let rules = restrictionInfo?.rules ?? [] + + return rules.contains(where: { $0.platform == "all" } ) + } // // MARK: Nicegram (extractReason) @@ -444,7 +461,7 @@ public extension PeerId { var isAnonymousSavedMessages: Bool { if self.namespace == Namespaces.Peer.CloudUser { - if self.id._internalGetInt64Value() == 2666000 { + if self.id._internalGetInt64Value() == anonymousSavedMessagesId { return true } } diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 95dfbdca6bb..bea3e909191 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -195,6 +195,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case videoMessagesPlayOnceSuggestion = 61 case incomingVideoMessagePlayOnceTip = 62 case outgoingVideoMessagePlayOnceTip = 63 + case dismissedMessageTagsBadge = 64 + case savedMessageTagLabelSuggestion = 65 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -504,6 +506,14 @@ private struct ApplicationSpecificNoticeKeys { static func outgoingVideoMessagePlayOnceTip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.outgoingVideoMessagePlayOnceTip.key) } + + static func dismissedMessageTagsBadge() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedMessageTagsBadge.key) + } + + static func savedMessageTagLabelSuggestion() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessageTagLabelSuggestion.key) + } } public struct ApplicationSpecificNotice { @@ -2080,4 +2090,52 @@ public struct ApplicationSpecificNotice { return Int(previousValue) } } + + public static func setDismissedMessageTagsBadge(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedMessageTagsBadge(), entry) + } + } + |> ignoreValues + } + + public static func dismissedMessageTagsBadge(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedMessageTagsBadge()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + |> 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) { + return value.value + } else { + return 0 + } + } + } + + public static func incrementSavedMessageTagLabelSuggestion(accountManager: AccountManager, count: Int = 1) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.savedMessageTagLabelSuggestion())?.get(ApplicationSpecificCounterNotice.self) { + currentValue = value.value + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.savedMessageTagLabelSuggestion(), entry) + } + + return Int(previousValue) + } + } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index 3f4587e2070..f13d955c4be 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -637,9 +637,9 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio highlightedFill: UIColor(rgb: 0xbaff93), stroke: bubbleStrokeColor, shadow: nil, - reactionInactiveBackground: UIColor(rgb: 0x2DA32F).withMultipliedAlpha(0.12), - reactionInactiveForeground: UIColor(rgb: 0x2DA32F), - reactionActiveBackground: UIColor(rgb: 0x2DA32F), + reactionInactiveBackground: UIColor(rgb: 0x3fc33b).withMultipliedAlpha(0.12), + reactionInactiveForeground: UIColor(rgb: 0x3fc33b), + reactionActiveBackground: UIColor(rgb: 0x3fc33b), reactionActiveForeground: .clear, reactionInactiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.2), reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.2) @@ -649,9 +649,9 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio highlightedFill: UIColor(rgb: 0xbaff93), stroke: bubbleStrokeColor, shadow: nil, - reactionInactiveBackground: UIColor(rgb: 0x2DA32F).withMultipliedAlpha(0.12), - reactionInactiveForeground: UIColor(rgb: 0x2DA32F), - reactionActiveBackground: UIColor(rgb: 0x2DA32F), + reactionInactiveBackground: UIColor(rgb: 0x3fc33b).withMultipliedAlpha(0.12), + reactionInactiveForeground: UIColor(rgb: 0x3fc33b), + reactionActiveBackground: UIColor(rgb: 0x3fc33b), reactionActiveForeground: .clear, reactionInactiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.2), reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.2) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 69f8693fac2..1131b41b527 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -20,6 +20,9 @@ public enum PresentationResourceKey: Int32 { case navigationShareIcon case navigationSearchIcon case navigationCompactSearchIcon + case navigationCompactSearchWhiteIcon + case navigationCompactTagsSearchIcon + case navigationCompactTagsSearchWhiteIcon case navigationCalendarIcon case navigationMoreIcon case navigationMoreCircledIcon @@ -71,6 +74,7 @@ public enum PresentationResourceKey: Int32 { case itemListCloudIcon case itemListTopicArrowIcon case itemListAddBoostsIcon + case itemListPremiumIcon case statsReactionsIcon case statsForwardsIcon @@ -212,6 +216,7 @@ public enum PresentationResourceKey: Int32 { case chatInputSearchPanelCalendarImage case chatInputSearchPanelMembersImage + case chatHistoryNavigationButtonBackground case chatHistoryNavigationButtonImage case chatHistoryNavigationUpButtonImage case chatHistoryMentionsButtonImage diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 1ead3d9cee2..8556e23efe2 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -586,15 +586,24 @@ public struct PresentationResourcesChat { }) } - - public static func chatHistoryNavigationButtonImage(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatHistoryNavigationButtonImage.rawValue, { theme in + public static func chatHistoryNavigationButtonBackground(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatHistoryNavigationButtonBackground.rawValue, { theme in return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) + context.setLineWidth(0.5) context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor) context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5))) - context.setStrokeColor(theme.chat.historyNavigation.foregroundColor.cgColor) + }) + }) + } + + public static func chatHistoryNavigationButtonImage(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatHistoryNavigationButtonImage.rawValue, { theme in + return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setStrokeColor(theme.rootController.navigationBar.accentTextColor.cgColor) context.setLineWidth(1.5) let position = CGPoint(x: 9.0 - 0.5, y: 23.0) @@ -610,10 +619,8 @@ public struct PresentationResourcesChat { return theme.image(PresentationResourceKey.chatHistoryNavigationUpButtonImage.rawValue, { theme in return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setLineWidth(0.5) - context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor) - context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5))) - context.setStrokeColor(theme.chat.historyNavigation.foregroundColor.cgColor) + + context.setStrokeColor(theme.rootController.navigationBar.accentTextColor.cgColor) context.setLineWidth(1.5) context.translateBy(x: size.width * 0.5, y: size.height * 0.5) @@ -632,11 +639,6 @@ public struct PresentationResourcesChat { return theme.image(PresentationResourceKey.chatHistoryMentionsButtonImage.rawValue, { theme in return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.chat.historyNavigation.fillColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: size.width - 1.0, height: size.height - 1.0))) - context.setLineWidth(0.5) - context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor) - context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5))) if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigateToMentions"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage { context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) @@ -649,11 +651,6 @@ public struct PresentationResourcesChat { return theme.image(PresentationResourceKey.chatHistoryReactionsButtonImage.rawValue, { theme in return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.chat.historyNavigation.fillColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: size.width - 1.0, height: size.height - 1.0))) - context.setLineWidth(0.5) - context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor) - context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5))) if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage { context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index c25c954ed68..67873fb57eb 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -282,6 +282,30 @@ public struct PresentationResourcesItemList { }) } + public static func premiumIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.itemListPremiumIcon.rawValue, { theme in + return generateImage(CGSize(width: 16.0, height: 16.0), contextGenerator: { size, context in + let bounds = CGRect(origin: .zero, size: size) + context.clear(bounds) + + let image = UIImage(bundleImageName: "Item List/PremiumIcon")! + context.clip(to: bounds, mask: image.cgImage!) + + let colorsArray: [CGColor] = [ + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x6b93ff).cgColor, + UIColor(rgb: 0x8d77ff).cgColor, + UIColor(rgb: 0xb56eec).cgColor, + UIColor(rgb: 0xb56eec).cgColor + ] + var locations: [CGFloat] = [0.0, 0.3, 0.5, 0.7, 1.0] + let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions()) + }) + }) + } + public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? { if !top && !bottom { return nil diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift index 0018fc54f46..342eb23fc44 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift @@ -73,6 +73,24 @@ public struct PresentationResourcesRootController { return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: theme.rootController.navigationBar.accentTextColor) }) } + + public static func navigationCompactSearchWhiteIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationCompactSearchWhiteIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: .white) + }) + } + + public static func navigationCompactTagsSearchIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationCompactTagsSearchIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigationSearchTagsIcon"), color: theme.rootController.navigationBar.accentTextColor) + }) + } + + public static func navigationCompactTagsSearchWhiteIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationCompactTagsSearchWhiteIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigationSearchTagsIcon"), color: .white) + }) + } public static func navigationCalendarIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationCalendarIcon.rawValue, { theme in diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 8e571763be4..7a4f7f38c27 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -455,6 +455,12 @@ swift_library( "//submodules/Components/BalancedTextComponent", "//submodules/TelegramUI/Components/VideoMessageCameraScreen", "//submodules/TelegramUI/Components/MediaScrubberComponent", + "//submodules/TelegramUI/Components/Chat/ChatShareMessageTagView", + "//submodules/PromptUI", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/Chat/TopMessageReactions", + "//submodules/TelegramUI/Components/Chat/SavedTagNameAlertController", + "//submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD new file mode 100644 index 00000000000..f82054dbad1 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatInlineSearchResultsListComponent", + module_name = "ChatInlineSearchResultsListComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/ComponentFlow", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/AccountContext", + "//submodules/ChatListUI", + "//submodules/MergeLists", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/UIKitRuntimeUtils", + "//submodules/ChatPresentationInterfaceState", + "//submodules/ContactsPeerItem", + "//submodules/ItemListUI", + "//submodules/ChatListSearchItemHeader", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift new file mode 100644 index 00000000000..81272ab15bb --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -0,0 +1,817 @@ +import UIKit +import ComponentFlow +import Display +import AsyncDisplayKit +import TelegramCore +import Postbox +import AccountContext +import ChatListUI +import MergeLists +import ComponentDisplayAdapters +import TelegramPresentationData +import SwiftSignalKit +import TelegramUIPreferences +import UIKitRuntimeUtils +import ChatPresentationInterfaceState +import ContactsPeerItem +import ItemListUI +import ChatListSearchItemHeader + +public final class ChatInlineSearchResultsListComponent: Component { + public struct Presentation: Equatable { + public var theme: PresentationTheme + public var strings: PresentationStrings + public var chatListFontSize: PresentationFontSize + public var dateTimeFormat: PresentationDateTimeFormat + public var nameSortOrder: PresentationPersonNameOrder + public var nameDisplayOrder: PresentationPersonNameOrder + + public init( + theme: PresentationTheme, + strings: PresentationStrings, + chatListFontSize: PresentationFontSize, + dateTimeFormat: PresentationDateTimeFormat, + nameSortOrder: PresentationPersonNameOrder, + nameDisplayOrder: PresentationPersonNameOrder + ) { + self.theme = theme + self.strings = strings + self.chatListFontSize = chatListFontSize + self.dateTimeFormat = dateTimeFormat + self.nameSortOrder = nameSortOrder + self.nameDisplayOrder = nameDisplayOrder + } + + public static func ==(lhs: Presentation, rhs: Presentation) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings != rhs.strings { + return false + } + if lhs.chatListFontSize != rhs.chatListFontSize { + return false + } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } + if lhs.nameSortOrder != rhs.nameSortOrder { + return false + } + if lhs.nameDisplayOrder != rhs.nameDisplayOrder { + return false + } + return true + } + } + + public enum Contents: Equatable { + case empty + case tag(MemoryBuffer) + case search(query: String, includeSavedPeers: Bool) + } + + public let context: AccountContext + public let presentation: Presentation + public let peerId: EnginePeer.Id + public let contents: Contents + public let insets: UIEdgeInsets + public let messageSelected: (EngineMessage) -> Void + public let peerSelected: (EnginePeer) -> Void + public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal? + public let getSearchResult: () -> Signal? + public let getSavedPeers: (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>? + + public init( + context: AccountContext, + presentation: Presentation, + peerId: EnginePeer.Id, + contents: Contents, + insets: UIEdgeInsets, + messageSelected: @escaping (EngineMessage) -> Void, + peerSelected: @escaping (EnginePeer) -> Void, + loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal?, + getSearchResult: @escaping () -> Signal?, + getSavedPeers: @escaping (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>? + ) { + self.context = context + self.presentation = presentation + self.peerId = peerId + self.contents = contents + self.insets = insets + self.messageSelected = messageSelected + self.peerSelected = peerSelected + self.loadTagMessages = loadTagMessages + self.getSearchResult = getSearchResult + self.getSavedPeers = getSavedPeers + } + + public static func ==(lhs: ChatInlineSearchResultsListComponent, rhs: ChatInlineSearchResultsListComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.presentation != rhs.presentation { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.contents != rhs.contents { + return false + } + if lhs.insets != rhs.insets { + return false + } + return true + } + + private enum Entry: Equatable, Comparable { + enum Id: Hashable { + case peer(EnginePeer.Id) + case message(EngineMessage.Id) + } + + case peer(EnginePeer) + case message(EngineMessage) + + var id: Id { + switch self { + case let .peer(peer): + return .peer(peer.id) + case let .message(message): + return .message(message.id) + } + } + + static func ==(lhs: Entry, rhs: Entry) -> Bool { + switch lhs { + case let .peer(peer): + if case .peer(peer) = rhs { + return true + } else { + return false + } + case let .message(message): + if case .message(message) = rhs { + return true + } else { + return false + } + } + } + + static func <(lhs: Entry, rhs: Entry) -> Bool { + switch lhs { + case let .peer(lhsPeer): + switch rhs { + case let .peer(rhsPeer): + if lhsPeer.debugDisplayTitle != rhsPeer.debugDisplayTitle { + return lhsPeer.debugDisplayTitle < rhsPeer.debugDisplayTitle + } + return lhsPeer.id < rhsPeer.id + case .message: + return true + } + case let .message(lhsMessage): + switch rhs { + case .peer: + return false + case let .message(rhsMessage): + return lhsMessage.index > rhsMessage.index + } + } + } + } + + private struct ContentsState: Equatable { + enum ContentId: Equatable { + case empty + case tag(MemoryBuffer) + case search(String) + } + + var id: Int + var contentId: ContentId + var entries: [Entry] + var messages: [EngineMessage] + var hasEarlier: Bool + var hasLater: Bool + + init(id: Int, contentId: ContentId, entries: [Entry], messages: [EngineMessage], hasEarlier: Bool, hasLater: Bool) { + self.id = id + self.contentId = contentId + self.entries = entries + self.messages = messages + self.hasEarlier = hasEarlier + self.hasLater = hasLater + } + } + + public final class View: UIView { + private var component: ChatInlineSearchResultsListComponent? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + + private let listNode: ListView + + private var tagContents: (index: MessageIndex?, disposable: Disposable?)? + private var searchContents: (index: MessageIndex?, disposable: Disposable?)? + + private var nextContentsId: Int = 0 + private var contentsState: ContentsState? + private var appliedContentsState: ContentsState? + + private var currentChatListPresentationData: (Presentation, ChatListPresentationData)? + private var chatListNodeInteraction: ChatListNodeInteraction? + + private let isReadyPromise = Promise() + private var didSetReady: Bool = false + public var isReady: Signal { + return self.isReadyPromise.get() + } + + override public init(frame: CGRect) { + self.listNode = ListView() + + super.init(frame: frame) + + self.addSubnode(self.listNode) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.tagContents?.disposable?.dispose() + self.searchContents?.disposable?.dispose() + } + + public func animateIn() { + self.listNode.layer.animateSublayerScale(from: 0.95, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + + if let blurFilter = makeBlurFilter() { + blurFilter.setValue(0.0 as NSNumber, forKey: "inputRadius") + self.listNode.layer.filters = [blurFilter] + self.listNode.layer.animate(from: 30.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak self] completed in + guard let self, completed else { + return + } + self.listNode.layer.filters = [] + }) + } + } + + public func animateOut() { + self.listNode.layer.animateSublayerScale(from: 1.0, to: 0.95, duration: 0.3, removeOnCompletion: false) + + if let blurFilter = makeBlurFilter() { + blurFilter.setValue(30.0 as NSNumber, forKey: "inputRadius") + self.listNode.layer.filters = [blurFilter] + self.listNode.layer.animate(from: 0.0 as NSNumber, to: 30.0 as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.3, removeOnCompletion: false) + } + } + + func update(component: ChatInlineSearchResultsListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let previousComponent = self.component + + self.component = component + self.state = state + + self.backgroundColor = component.presentation.theme.list.plainBackgroundColor + + self.listNode.frame = CGRect(origin: CGPoint(), size: availableSize) + let (listDuration, listCurve) = listViewAnimationDurationAndCurve(transition: transition.containedViewLayoutTransition) + self.listNode.transaction( + deleteIndices: [], + insertIndicesAndItems: [], + updateIndicesAndItems: [], + options: [.Synchronous, .LowLatency, .PreferSynchronousDrawing, .PreferSynchronousResourceLoading], + updateSizeAndInsets: ListViewUpdateSizeAndInsets( + size: availableSize, + insets: component.insets, + duration: listDuration, + curve: listCurve + ), + updateOpaqueState: nil + ) + + self.listNode.displayedItemRangeChanged = { [weak self] displayedRange, opaqueTransactionState in + guard let self else { + return + } + guard let stateId = opaqueTransactionState as? Int else { + return + } + guard let contentsState = self.contentsState, contentsState.id == stateId else { + return + } + guard let visibleRange = displayedRange.visibleRange else { + return + } + var loadAroundIndex: MessageIndex? + if visibleRange.firstIndex <= 5 { + if contentsState.hasLater { + loadAroundIndex = contentsState.messages.first?.index + } + } else if visibleRange.lastIndex >= contentsState.messages.count - 5 { + if contentsState.hasEarlier { + loadAroundIndex = contentsState.messages.last?.index + } + } + + if let (currentIndex, disposable) = self.tagContents { + if let loadAroundIndex, loadAroundIndex != currentIndex { + switch component.contents { + case .empty: + break + case let .tag(tag): + disposable?.dispose() + let updatedDisposable = MetaDisposable() + self.tagContents = (loadAroundIndex, updatedDisposable) + + if let historySignal = component.loadTagMessages(tag, self.tagContents?.index) { + updatedDisposable.set((historySignal + |> deliverOnMainQueue).startStrict(next: { [weak self] view in + guard let self else { + return + } + + let messages = view.entries.reversed().map { entry in + return EngineMessage(entry.message) + } + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .tag(tag), + entries: messages.map { message in + return .message(message) + }, + messages: messages, + hasEarlier: view.earlierId != nil, + hasLater: view.laterId != nil + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + })) + } + case .search: + break + } + } + } + } + + switch component.contents { + case .empty: + if previousComponent?.contents != component.contents { + self.tagContents?.disposable?.dispose() + self.tagContents = nil + + self.searchContents?.disposable?.dispose() + self.searchContents = nil + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .empty, + entries: [], + messages: [], + hasEarlier: false, + hasLater: false + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + } + case let .tag(tag): + if previousComponent?.contents != component.contents { + self.tagContents?.disposable?.dispose() + self.tagContents = nil + + self.searchContents?.disposable?.dispose() + self.searchContents = nil + + let disposable = MetaDisposable() + self.tagContents = (nil, disposable) + + if let historySignal = component.loadTagMessages(tag, self.tagContents?.index) { + disposable.set((historySignal + |> deliverOnMainQueue).startStrict(next: { [weak self] view in + guard let self else { + return + } + + let messages = view.entries.reversed().map { entry in + return EngineMessage(entry.message) + } + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .tag(tag), + entries: messages.map { message in + return .message(message) + }, + messages: messages, + hasEarlier: view.earlierId != nil, + hasLater: view.laterId != nil + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + })) + } + } + case let .search(query, includeSavedPeers): + if previousComponent?.contents != component.contents { + self.tagContents?.disposable?.dispose() + self.tagContents = nil + + self.searchContents?.disposable?.dispose() + self.searchContents = nil + + let disposable = MetaDisposable() + self.searchContents = (nil, disposable) + + let savedPeers: Signal<[(EnginePeer, MessageIndex?)], NoError> + if includeSavedPeers, !query.isEmpty, let savedPeersSignal = component.getSavedPeers(query) { + savedPeers = savedPeersSignal + } else { + savedPeers = .single([]) + } + + if let historySignal = component.getSearchResult() { + disposable.set((savedPeers + |> mapToSignal { savedPeers -> Signal<([(EnginePeer, MessageIndex?)], SearchMessagesResult?), NoError> in + if savedPeers.isEmpty { + return historySignal + |> map { result in + return ([], result) + } + } else { + return (.single(nil) |> then(historySignal)) + |> map { result in + return (savedPeers, result) + } + } + } + |> deliverOnMainQueue).startStrict(next: { [weak self] savedPeers, result in + guard let self else { + return + } + + let messages: [EngineMessage] = result?.messages.map { entry in + return EngineMessage(entry) + } ?? [] + + var entries: [Entry] = [] + for (peer, _) in savedPeers { + entries.append(.peer(peer)) + } + for message in messages { + entries.append(.message(message)) + } + entries.sort() + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .search(query), + entries: entries, + messages: messages, + hasEarlier: false, + hasLater: false + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + })) + } + } + } + + if let contentsState = self.contentsState, self.contentsState != self.appliedContentsState { + let previousContentsState = self.appliedContentsState + self.appliedContentsState = self.contentsState + + let chatListNodeInteraction: ChatListNodeInteraction + if let current = self.chatListNodeInteraction { + chatListNodeInteraction = current + } else { + chatListNodeInteraction = ChatListNodeInteraction( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + activateSearch: { + }, + peerSelected: { _, _, _, _ in + }, + disabledPeerSelected: { _, _, _ in + }, + togglePeerSelected: { _, _ in + }, + togglePeersSelection: { _, _ in + }, + additionalCategorySelected: { _ in + }, + messageSelected: { [weak self] _, _, message, _ in + guard let self else { + return + } + self.listNode.clearHighlightAnimated(true) + + self.component?.messageSelected(message) + }, + groupSelected: { _ in + }, + addContact: { _ in + }, + setPeerIdWithRevealedOptions: { _, _ in + }, + setItemPinned: { _, _ in + }, + setPeerMuted: { _, _ in + }, + setPeerThreadMuted: { _, _, _ in + }, + deletePeer: { _, _ in + }, + deletePeerThread: { _, _ in + }, + setPeerThreadStopped: { _, _, _ in + }, + setPeerThreadPinned: { _, _, _ in + }, + setPeerThreadHidden: { _, _, _ in + }, + updatePeerGrouping: { _, _ in + }, + togglePeerMarkedUnread: { _, _ in + }, + toggleArchivedFolderHiddenByDefault: { + }, + toggleThreadsSelection: { _, _ in + }, + hidePsa: { _ in + }, + activateChatPreview: { item, _, node, gesture, _ in + gesture?.cancel() + }, + present: { _ in + }, + openForumThread: { _, _ in + }, + openStorageManagement: { + }, + openPasswordSetup: { + }, + openPremiumIntro: { + }, + openPremiumGift: { + }, + openActiveSessions: { + }, + performActiveSessionAction: { _, _ in + }, + openChatFolderUpdates: { + }, + hideChatFolderUpdates: { + }, + openStories: { _, _ in + }, + dismissNotice: { _ in + } + ) + self.chatListNodeInteraction = chatListNodeInteraction + } + + var searchTextHighightState: String? + if case let .search(query, _) = component.contents, !query.isEmpty { + searchTextHighightState = query.lowercased() + } + + var allUpdated = false + if chatListNodeInteraction.searchTextHighightState != searchTextHighightState { + chatListNodeInteraction.searchTextHighightState = searchTextHighightState + allUpdated = true + } + + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates( + leftList: previousContentsState?.entries ?? [], + rightList: contentsState.entries, + isLess: { lhs, rhs in + return lhs < rhs + }, + isEqual: { lhs, rhs in + return lhs == rhs + }, + getId: { entry in + return entry.id + }, + allUpdated: allUpdated + ) + + let displayMessagesHeader = contentsState.entries.count != contentsState.messages.count + + let chatListPresentationData: ChatListPresentationData + if let current = self.currentChatListPresentationData, current.0 == component.presentation { + chatListPresentationData = current.1 + } else { + chatListPresentationData = ChatListPresentationData( + theme: component.presentation.theme, + fontSize: component.presentation.chatListFontSize, + strings: component.presentation.strings, + dateTimeFormat: component.presentation.dateTimeFormat, + nameSortOrder: component.presentation.nameSortOrder, + nameDisplayOrder: component.presentation.nameDisplayOrder, + disableAnimations: false + ) + self.currentChatListPresentationData = (component.presentation, chatListPresentationData) + } + + let listPresentationData = ItemListPresentationData(component.context.sharedContext.currentPresentationData.with({ $0 })) + let peerSelected = component.peerSelected + + let entryToItem: (Entry) -> ListViewItem = { entry -> ListViewItem in + switch entry { + case let .peer(peer): + return ContactsPeerItem( + presentationData: listPresentationData, + sortOrder: component.presentation.nameSortOrder, + displayOrder: component.presentation.nameDisplayOrder, + context: component.context, + peerMode: .generalSearch(isSavedMessages: true), + peer: .peer(peer: peer, chatPeer: peer), + status: .none, + badge: nil, + requiresPremiumForMessaging: false, + enabled: true, + selection: .none, + editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), + index: nil, + header: displayMessagesHeader ? ChatListSearchItemHeader(type: .chats, theme: listPresentationData.theme, strings: listPresentationData.strings) : nil, + action: { [weak self] peer in + self?.listNode.clearHighlightAnimated(true) + + if case let .peer(peer?, _) = peer { + peerSelected(peer) + } + }, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer + ) + case let .message(message): + var effectiveAuthor: EnginePeer? + + if let forwardInfo = message.forwardInfo { + effectiveAuthor = forwardInfo.author.flatMap(EnginePeer.init) + if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { + effectiveAuthor = EnginePeer(TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)) + } + } + if let sourceAuthorInfo = message._asMessage().sourceAuthorInfo { + if let originalAuthor = sourceAuthorInfo.originalAuthor, let peer = message.peers[originalAuthor] { + effectiveAuthor = EnginePeer(peer) + } else if let authorSignature = sourceAuthorInfo.originalAuthorName { + effectiveAuthor = EnginePeer(TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)) + } + } + if effectiveAuthor == nil { + effectiveAuthor = message.author + } + + let renderedPeer: EngineRenderedPeer + if let effectiveAuthor { + renderedPeer = EngineRenderedPeer(peer: effectiveAuthor) + } else { + renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: [:], associatedMedia: [:]) + } + + return ChatListItem( + presentationData: chatListPresentationData, + context: component.context, + chatListLocation: component.peerId == component.context.account.peerId ? .savedMessagesChats : .chatList(groupId: .root), + filterData: nil, + index: .forum( + pinnedIndex: .none, + timestamp: message.timestamp, + threadId: message.threadId ?? component.context.account.peerId.toInt64(), + namespace: message.id.namespace, + id: message.id.id + ), + content: .peer(ChatListItemContent.PeerData( + messages: [message], + peer: renderedPeer, + threadInfo: nil, + combinedReadState: nil, + isRemovedFromTotalUnreadCount: false, + presence: nil, + hasUnseenMentions: false, + hasUnseenReactions: false, + draftState: nil, + inputActivities: nil, + promoInfo: nil, + ignoreUnreadBadge: false, + displayAsMessage: component.peerId != component.context.account.peerId, + hasFailedMessages: false, + forumTopicData: nil, + topForumTopicItems: [], + autoremoveTimeout: nil, + storyState: nil, + requiresPremiumForMessaging: false, + displayAsTopicList: false + )), + editing: false, + hasActiveRevealControls: false, + selected: false, + header: displayMessagesHeader ? ChatListSearchItemHeader(type: .messages(location: nil), theme: listPresentationData.theme, strings: listPresentationData.strings) : nil, + enableContextActions: false, + hiddenOffset: false, + interaction: chatListNodeInteraction + ) + } + } + + var scrollToItem: ListViewScrollToItem? + if previousContentsState?.contentId != contentsState.contentId && !contentsState.entries.isEmpty { + scrollToItem = ListViewScrollToItem( + index: 0, + position: .top(0.0), + animated: false, + curve: .Default(duration: nil), + directionHint: .Up + ) + } + + self.listNode.transaction( + deleteIndices: deleteIndices.map { index in + return ListViewDeleteItem(index: index, directionHint: nil) + }, + insertIndicesAndItems: indicesAndItems.map { index, item, previousIndex in + return ListViewInsertItem( + index: index, + previousIndex: previousIndex, + item: entryToItem(item), + directionHint: nil, + forceAnimateInsertion: false + ) + }, + updateIndicesAndItems: updateIndices.map { index, item, previousIndex in + return ListViewUpdateItem( + index: index, + previousIndex: previousIndex, + item: entryToItem(item), + directionHint: nil + ) + }, + options: [.Synchronous, .LowLatency, .PreferSynchronousDrawing, .PreferSynchronousResourceLoading], + scrollToItem: scrollToItem, + updateSizeAndInsets: nil, + updateOpaqueState: contentsState.id + ) + } + + 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, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 31efcae6faa..99dad21cf3c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1042,6 +1042,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -1049,7 +1050,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -1255,8 +1256,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { presentationData: item.presentationData, presentationContext: item.controllerInteraction.presentationContext, availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: reactions, message: item.message, + associatedData: item.associatedData, accountPeer: item.associatedData.accountPeer, isIncoming: item.message.effectivelyIncoming(item.context.account.peerId), constrainedWidth: maxReactionsWidth @@ -1684,7 +1687,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { guard let strongSelf = weakSelf.value, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } reactionButtonsNode.openReactionPreview = { gesture, sourceView, value in guard let strongSelf = weakSelf.value, let item = strongSelf.item else { @@ -1776,8 +1779,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { case let .optionalAction(f): f() case let .openContextMenu(openContextMenu): - if canAddMessageReactions(message: item.message) { - item.controllerInteraction.updateMessageReaction(item.message, .default) + // MARK: Nicegram HideReactions, account added + if canAddMessageReactions(message: item.message, account: item.context.account) { + item.controllerInteraction.updateMessageReaction(item.message, .default, false) } else { item.controllerInteraction.openMessageContextMenu(openContextMenu.tapMessage, openContextMenu.selectAll, self, openContextMenu.subFrame, nil, nil) } @@ -1785,8 +1789,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if case .tap = gesture { item.controllerInteraction.clickThroughMessage() } else if case .doubleTap = gesture { - if canAddMessageReactions(message: item.message) { - item.controllerInteraction.updateMessageReaction(item.message, .default) + // MARK: Nicegram HideReactions, account added + if canAddMessageReactions(message: item.message, account: item.context.account) { + item.controllerInteraction.updateMessageReaction(item.message, .default, false) } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift index ae5c65eeda7..4298a2546db 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift @@ -83,13 +83,13 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton }) } - public typealias AsyncLayout = (_ width: CGFloat, _ iconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ inProgress: Bool, _ drawBackground: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode)) + public typealias AsyncLayout = (_ width: CGFloat, _ sideInset: CGFloat?, _ iconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ inProgress: Bool, _ drawBackground: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode)) public static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> AsyncLayout { let previousRegularIconImage = current?.regularIconImage let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout) - return { width, iconImage, cornerIcon, title, titleColor, inProgress, drawBackground in + return { width, sideInset, iconImage, cornerIcon, title, titleColor, inProgress, drawBackground in let targetNode: ChatMessageAttachedContentButtonNode if let current = current { targetNode = current @@ -114,7 +114,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton iconWidth = iconImage.size.width + 5.0 } - let labelInset: CGFloat = 8.0 + let labelInset: CGFloat = sideInset ?? 8.0 let (textSize, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0 - iconWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets())) @@ -146,7 +146,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: refinedWidth, height: size.height)) - var textFrame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size) + var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((refinedWidth - textSize.size.width) / 2.0), y: floorToScreenPixels((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size) if drawBackground { textFrame.origin.y += 1.0 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index cc4c9ecd40b..7e507c47c6c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -31,6 +31,7 @@ import ChatMessageAttachedContentButtonNode import MessageInlineBlockBackgroundView import ComponentFlow import PlainButtonComponent +import AvatarNode public enum ChatMessageAttachedContentActionIcon { case instant @@ -55,6 +56,28 @@ public struct ChatMessageAttachedContentNodeMediaFlags: OptionSet { } public final class ChatMessageAttachedContentNode: ASDisplayNode { + private enum InlineMedia: Equatable { + case media(Media) + case peerAvatar(EnginePeer) + + static func ==(lhs: InlineMedia, rhs: InlineMedia) -> Bool { + switch lhs { + case let .media(lhsMedia): + if case let .media(rhsMedia) = rhs { + return lhsMedia.isSemanticallyEqual(to: rhsMedia) + } else { + return false + } + case let .peerAvatar(lhsPeer): + if case let .peerAvatar(rhsPeer) = rhs { + return lhsPeer.largeProfileImage == rhsPeer.largeProfileImage + } else { + return false + } + } + } + } + private var backgroundView: MessageInlineBlockBackgroundView? private let transformContainer: ASDisplayNode @@ -72,7 +95,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { private var closeButton: ComponentView? private var closeButtonImage: UIImage? - private var inlineMediaValue: Media? + private var inlineMediaValue: InlineMedia? //private var additionalImageBadgeNode: ChatMessageInteractiveMediaBadge? private var linkHighlightingNode: LinkHighlightingNode? @@ -281,6 +304,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } var contentMediaInline = false + var contentMediaImagePeer: EnginePeer? + if let (media, flags) = mediaAndFlags { contentMediaInline = flags.contains(.preferMediaInline) @@ -326,21 +351,25 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } else if let _ = media as? TelegramMediaStory { contentMediaValue = media } + } else if let adAttribute = message.adAttribute, case let .join(_, _, peer) = adAttribute.target, let peer, peer.largeProfileImage != nil { + contentMediaInline = true + contentMediaImagePeer = peer } var maxWidth: CGFloat = .greatestFiniteMagnitude let contentMediaContinueLayout: ((CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)))? - let inlineMediaAndSize: (Media, CGSize)? + + let inlineMediaAndSize: (InlineMedia, CGSize)? if let contentMediaValue { if contentMediaInline { contentMediaContinueLayout = nil if let image = contentMediaValue as? TelegramMediaImage { - inlineMediaAndSize = (image, CGSize(width: 54.0, height: 54.0)) + inlineMediaAndSize = (.media(image), CGSize(width: 54.0, height: 54.0)) } else if let file = contentMediaValue as? TelegramMediaFile, !file.previewRepresentations.isEmpty { - inlineMediaAndSize = (file, CGSize(width: 54.0, height: 54.0)) + inlineMediaAndSize = (.media(file), CGSize(width: 54.0, height: 54.0)) } else { inlineMediaAndSize = nil } @@ -368,6 +397,9 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { inlineMediaAndSize = nil } + } else if let contentMediaImagePeer { + contentMediaContinueLayout = nil + inlineMediaAndSize = (.peerAvatar(contentMediaImagePeer), CGSize(width: 54.0, height: 54.0)) } else { contentMediaContinueLayout = nil inlineMediaAndSize = nil @@ -555,6 +587,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { let (buttonWidth, continueLayout) = makeActionButtonLayout( maxContentsWidth, + nil, buttonIconImage, cornerIcon, actionTitle, @@ -654,6 +687,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { ), constrainedSize: CGSize(width: maxStatusContentWidth, height: CGFloat.greatestFiniteMagnitude), availableReactions: associatedData.availableReactions, + savedMessageTags: associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -661,7 +695,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { replyCount: dateReplies, isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: message), + canViewReactionList: canViewMessageReactionList(message: message, isInline: associatedData.isInline), animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer )) @@ -905,7 +939,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { inlineMedia = current if let curentInlineMediaValue = self.inlineMediaValue { - updateMedia = !curentInlineMediaValue.isSemanticallyEqual(to: inlineMediaValue) + updateMedia = curentInlineMediaValue != inlineMediaValue } else { updateMedia = true } @@ -928,25 +962,58 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { self.inlineMediaValue = inlineMediaValue var fittedImageSize = inlineMediaSize - if let image = inlineMediaValue as? TelegramMediaImage { - if let dimensions = image.representations.last?.dimensions.cgSize { - fittedImageSize = dimensions.aspectFilled(inlineMediaSize) - } - } else if let file = inlineMediaValue as? TelegramMediaFile { - if let dimensions = file.dimensions?.cgSize { - fittedImageSize = dimensions.aspectFilled(inlineMediaSize) + switch inlineMediaValue { + case let .media(inlineMediaValue): + if let image = inlineMediaValue as? TelegramMediaImage { + if let dimensions = image.representations.last?.dimensions.cgSize { + fittedImageSize = dimensions.aspectFilled(inlineMediaSize) + } + } else if let file = inlineMediaValue as? TelegramMediaFile { + if let dimensions = file.dimensions?.cgSize { + fittedImageSize = dimensions.aspectFilled(inlineMediaSize) + } } + case .peerAvatar: + fittedImageSize = inlineMediaSize } if updateMedia { let resolvedInlineMediaValue = inlineMediaValue - if let image = resolvedInlineMediaValue as? TelegramMediaImage { - let updateInlineImageSignal = chatWebpageSnippetPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image), placeholderColor: mainColor.withMultipliedAlpha(0.1)) - inlineMedia.setSignal(updateInlineImageSignal) - } else if let file = resolvedInlineMediaValue as? TelegramMediaFile, let representation = file.previewRepresentations.last { - let updateInlineImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .peer(message.id.peerId), mediaReference: .message(message: MessageReference(message), media: file), representation: representation) - inlineMedia.setSignal(updateInlineImageSignal) + switch resolvedInlineMediaValue { + case let .media(resolvedInlineMediaValue): + if let image = resolvedInlineMediaValue as? TelegramMediaImage { + let updateInlineImageSignal = chatWebpageSnippetPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image), placeholderColor: mainColor.withMultipliedAlpha(0.1)) + inlineMedia.setSignal(updateInlineImageSignal) + } else if let file = resolvedInlineMediaValue as? TelegramMediaFile, let representation = file.previewRepresentations.last { + let updateInlineImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .peer(message.id.peerId), mediaReference: .message(message: MessageReference(message), media: file), representation: representation) + inlineMedia.setSignal(updateInlineImageSignal) + } + case let .peerAvatar(peer): + if let peerReference = PeerReference(peer._asPeer()) { + if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: peer.largeProfileImage, displayDimensions: inlineMediaSize, clipStyle: .none, blurred: false, inset: 0.0, emptyColor: mainColor.withMultipliedAlpha(0.1), synchronousLoad: synchronousLoads, provideUnrounded: false) { + let updateInlineImageSignal = signal |> map { images -> (TransformImageArguments) -> DrawingContext? in + let image = images?.0 + + return { arguments in + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) else { + return nil + } + + context.withFlippedContext { c in + if let cgImage = image?.cgImage { + c.draw(cgImage, in: CGRect(origin: CGPoint(), size: arguments.drawingSize)) + } + } + + addCorners(context, arguments: arguments) + + return context + } + } + inlineMedia.setSignal(updateInlineImageSignal) + } + } } } @@ -1151,17 +1218,23 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { self.contentMedia?.removeFromSupernode() self.contentMedia = contentMedia + contentMedia.activatePinch = { [weak controllerInteraction] sourceNode in + guard let controllerInteraction else { + return + } + controllerInteraction.activateMessagePinch(sourceNode) + } contentMedia.activateLocalContent = { [weak self] mode in guard let self else { return } self.openMedia?(mode) } - contentMedia.updateMessageReaction = { [weak controllerInteraction] message, value in + contentMedia.updateMessageReaction = { [weak controllerInteraction] message, value, force in guard let controllerInteraction else { return } - controllerInteraction.updateMessageReaction(message, value) + controllerInteraction.updateMessageReaction(message, value, force) } contentMedia.visibility = self.visibility != .none @@ -1279,11 +1352,11 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { self.statusNode = statusNode self.addSubnode(statusNode) - statusNode.reactionSelected = { [weak self] value in + statusNode.reactionSelected = { [weak self] _, value in guard let self, let message = self.message else { return } - controllerInteraction.updateMessageReaction(message, .reaction(value)) + controllerInteraction.updateMessageReaction(message, .reaction(value), false) } statusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index b58431bf169..010b9d6c5d5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2204,6 +2204,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -2211,7 +2212,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI replyCount: dateReplies, isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: message), + canViewReactionList: canViewMessageReactionList(message: message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -2522,8 +2523,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI presentationData: item.presentationData, presentationContext: item.controllerInteraction.presentationContext, availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: bubbleReactions, message: item.message, + associatedData: item.associatedData, accountPeer: item.associatedData.accountPeer, isIncoming: incoming, constrainedWidth: maximumNodeWidth @@ -3898,7 +3901,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI guard let strongSelf = strongSelf, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } reactionButtonsNode.openReactionPreview = { [weak strongSelf] gesture, sourceNode, value in guard let strongSelf = strongSelf, let item = strongSelf.item else { @@ -3987,13 +3990,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI // if let shareButtonNode = strongSelf.shareButtonNode { - let currentBackgroundFrame = strongSelf.backgroundNode.frame let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments) - - var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? currentBackgroundFrame.minX - buttonSize.width : currentBackgroundFrame.maxX + 8.0, y: currentBackgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize) + var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize) if let shareButtonOffset = shareButtonOffset { - buttonFrame.origin.x = shareButtonOffset.x + if incoming { + buttonFrame.origin.x = shareButtonOffset.x + } buttonFrame.origin.y = buttonFrame.origin.y + shareButtonOffset.y - (buttonSize.height - 30.0) } else if !disablesComments { buttonFrame.origin.y = buttonFrame.origin.y - (buttonSize.height - 30.0) @@ -4212,8 +4215,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI case let .optionalAction(f): f() case let .openContextMenu(openContextMenu): - if canAddMessageReactions(message: openContextMenu.tapMessage) { - item.controllerInteraction.updateMessageReaction(openContextMenu.tapMessage, .default) + // MARK: Nicegram HideReactions, account added + if canAddMessageReactions(message: openContextMenu.tapMessage, account: item.context.account) { + item.controllerInteraction.updateMessageReaction(openContextMenu.tapMessage, .default, false) } else { item.controllerInteraction.openMessageContextMenu(openContextMenu.tapMessage, openContextMenu.selectAll, self, openContextMenu.subFrame, nil, nil) } @@ -4221,8 +4225,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else if case .tap = gesture { item.controllerInteraction.clickThroughMessage() } else if case .doubleTap = gesture { - if canAddMessageReactions(message: item.message) { - item.controllerInteraction.updateMessageReaction(item.message, .default) + // MARK: Nicegram HideReactions, account added + if canAddMessageReactions(message: item.message, account: item.context.account) { + item.controllerInteraction.updateMessageReaction(item.message, .default, false) } } } @@ -4915,6 +4920,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let selectionNode = self.selectionNode { selectionNode.updateSelected(selected, animated: animated) let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentSize.width, height: self.contentSize.height)) + selectionNode.frame = selectionFrame selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left) self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index 2007ed9f716..a5cb3a43686 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -55,11 +55,11 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { self.addButtonNode.addTarget(self, action: #selector(self.addButtonPressed), forControlEvents: .touchUpInside) self.messageButtonNode.addTarget(self, action: #selector(self.messageButtonPressed), forControlEvents: .touchUpInside) - self.dateAndStatusNode.reactionSelected = { [weak self] value in + self.dateAndStatusNode.reactionSelected = { [weak self] _, value in guard let strongSelf = self, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } self.dateAndStatusNode.openReactionPreview = { [weak self] gesture, sourceView, value in @@ -286,6 +286,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -293,7 +294,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -306,7 +307,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor } - let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false) + let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, 10.0, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false) let addTitle: String if !canMessage && !canAdd { @@ -318,7 +319,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { addTitle = item.presentationData.strings.Conversation_ContactAddContactLong } } - let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, nil, false, addTitle.uppercased(), mainColor, false, false) + let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, 10.0, nil, false, addTitle.uppercased(), mainColor, false, false) let maxButtonWidth = max(messageButtonWidth, addButtonWidth) var maxContentWidth: CGFloat = avatarSize.width + 7.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift index e5b2bfb4ab1..03d14c86d34 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift @@ -225,6 +225,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var layoutInput: LayoutInput var constrainedSize: CGSize var availableReactions: AvailableReactions? + var savedMessageTags: SavedMessageTags? var reactions: [MessageReaction] var reactionPeers: [(MessageReaction.Reaction, EnginePeer)] var displayAllReactionPeers: Bool @@ -246,6 +247,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { layoutInput: LayoutInput, constrainedSize: CGSize, availableReactions: AvailableReactions?, + savedMessageTags: SavedMessageTags?, reactions: [MessageReaction], reactionPeers: [(MessageReaction.Reaction, EnginePeer)], displayAllReactionPeers: Bool, @@ -265,6 +267,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { self.type = type self.layoutInput = layoutInput self.availableReactions = availableReactions + self.savedMessageTags = savedMessageTags self.constrainedSize = constrainedSize self.reactions = reactions self.reactionPeers = reactionPeers @@ -316,7 +319,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } } } - public var reactionSelected: ((MessageReaction.Reaction) -> Void)? + public var reactionSelected: ((ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void)? public var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? override public init() { @@ -391,6 +394,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { selectedForeground: themeColors.reactionActiveForeground.argb, extractedBackground: arguments.presentationData.theme.theme.contextMenu.backgroundColor.argb, extractedForeground: arguments.presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedSelectedForeground: arguments.presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : arguments.presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb, deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb, selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb ) @@ -403,7 +407,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { deselectedForeground: themeColors.reactionInactiveForeground.argb, selectedForeground: themeColors.reactionActiveForeground.argb, extractedBackground: arguments.presentationData.theme.theme.contextMenu.backgroundColor.argb, - extractedForeground: arguments.presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedForeground: arguments.presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedSelectedForeground: arguments.presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : arguments.presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb, deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb, selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb ) @@ -739,11 +744,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { resultingHeight = layoutSize.height reactionButtonsResult = reactionButtonsContainer.update( context: arguments.context, - action: { value in + action: { itemNode, value in guard let strongSelf = self else { return } - strongSelf.reactionSelected?(value) + strongSelf.reactionSelected?(itemNode, value) }, reactions: [], colors: reactionColors, @@ -759,11 +764,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { reactionButtonsResult = reactionButtonsContainer.update( context: arguments.context, - action: { value in + action: { itemNode, value in guard let strongSelf = self else { return } - strongSelf.reactionSelected?(value) + strongSelf.reactionSelected?(itemNode, value) }, reactions: arguments.reactions.map { reaction in var centerAnimation: TelegramMediaFile? @@ -797,11 +802,21 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } } + var title: String? + if arguments.areReactionsTags, let savedMessageTags = arguments.savedMessageTags { + for tag in savedMessageTags.tags { + if tag.reaction == reaction.value { + title = tag.title + } + } + } + return ReactionButtonsAsyncLayoutContainer.Reaction( reaction: ReactionButtonComponent.Reaction( value: reaction.value, centerAnimation: centerAnimation, - animationFileId: animationFileId + animationFileId: animationFileId, + title: title ), count: Int(reaction.count), peers: arguments.areReactionsTags ? [] : peers, @@ -815,11 +830,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else { reactionButtonsResult = reactionButtonsContainer.update( context: arguments.context, - action: { value in + action: { itemNode, value in guard let strongSelf = self else { return } - strongSelf.reactionSelected?(value) + strongSelf.reactionSelected?(itemNode, value) }, reactions: [], colors: reactionColors, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift index 386ffb0fd3b..dcb04f0286c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift @@ -64,11 +64,11 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } } - self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] value in + self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] _, value in guard let strongSelf = self, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } self.interactiveFileNode.dateAndStatusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index bc3e25fceb8..5db10bc728b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -130,11 +130,11 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.dateAndStatusNode.reactionSelected = { [weak self] value in + self.dateAndStatusNode.reactionSelected = { [weak self] _, value in guard let strongSelf = self, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } self.dateAndStatusNode.openReactionPreview = { [weak self] gesture, sourceView, value in @@ -516,6 +516,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -523,7 +524,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -536,7 +537,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, titleColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor } - let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Chat_Giveaway_Message_LearnMore.uppercased(), titleColor, false, true) + let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, nil, false, item.presentationData.strings.Chat_Giveaway_Message_LearnMore.uppercased(), titleColor, false, true) let animationName: String let months = giveaway?.months ?? 0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift index 71bcca0c736..bf0182a2c2d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift @@ -144,11 +144,11 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN } } - self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] value in + self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] _, value in guard let strongSelf = self, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } self.interactiveFileNode.dateAndStatusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index 52b09d2731d..0b7c69613fd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -610,8 +610,10 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco presentationData: item.presentationData, presentationContext: item.controllerInteraction.presentationContext, availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: reactions, message: item.message, + associatedData: item.associatedData, accountPeer: item.associatedData.accountPeer, isIncoming: item.message.effectivelyIncoming(item.context.account.peerId), constrainedWidth: maxReactionsWidth @@ -832,7 +834,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco guard let strongSelf = weakSelf.value, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } reactionButtonsNode.openReactionPreview = { gesture, sourceNode, value in guard let strongSelf = weakSelf.value, let item = strongSelf.item else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index b161135180a..eb2328930ea 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -1075,6 +1075,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { layoutInput: statusLayoutInput, constrainedSize: constrainedSize, availableReactions: arguments.associatedData.availableReactions, + savedMessageTags: arguments.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: arguments.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -1082,7 +1083,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { replyCount: dateReplies, isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode, hasAutoremove: arguments.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: arguments.message), + canViewReactionList: canViewMessageReactionList(message: arguments.message, isInline: arguments.associatedData.isInline), animationCache: arguments.controllerInteraction.presentationContext.animationCache, animationRenderer: arguments.controllerInteraction.presentationContext.animationRenderer )) @@ -1207,18 +1208,15 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { strongSelf.audioTranscriptionState = updatedAudioTranscriptionState } - /*switch updatedAudioTranscriptionState { + switch updatedAudioTranscriptionState { case .expanded: info?.setInvertOffsetDirection() default: - break - } - } else if strongSelf.isWaitingForCollapse { - strongSelf.isWaitingForCollapse = false - info?.setInvertOffsetDirection() - }*/ - - info?.setInvertOffsetDirection() + if strongSelf.isWaitingForCollapse { + strongSelf.isWaitingForCollapse = false + info?.setInvertOffsetDirection() + } + } if let consumableContentIcon = consumableContentIcon { if strongSelf.consumableContentNode.supernode == nil { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index 84ca40fbbd3..ce300166140 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -574,6 +574,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -581,7 +582,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 2143e65f5c4..6472e55853a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -455,7 +455,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr public var activateLocalContent: (InteractiveMediaNodeActivateContent) -> Void = { _ in } public var activatePinch: ((PinchSourceContainerNode) -> Void)? - public var updateMessageReaction: ((Message, ChatControllerInteractionReaction) -> Void)? + public var updateMessageReaction: ((Message, ChatControllerInteractionReaction, Bool) -> Void)? override public init() { self.pinchContainerNode = PinchSourceContainerNode() @@ -873,6 +873,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), availableReactions: associatedData.availableReactions, + savedMessageTags: associatedData.savedMessageTags, reactions: dateAndStatus.dateReactions, reactionPeers: dateAndStatus.dateReactionPeers, displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -880,7 +881,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr replyCount: dateAndStatus.dateReplies, isPinned: dateAndStatus.isPinned, hasAutoremove: message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: message), + canViewReactionList: canViewMessageReactionList(message: message, isInline: associatedData.isInline), animationCache: presentationContext.animationCache, animationRenderer: presentationContext.animationRenderer )) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD index 0d4b8170026..0625afc28a2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD @@ -1,5 +1,9 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +NGDEPS = [ + "//Nicegram/NGData:NGData", +] + swift_library( name = "ChatMessageItemCommon", module_name = "ChatMessageItemCommon", @@ -9,7 +13,7 @@ swift_library( copts = [ "-warnings-as-errors", ], - deps = [ + deps = NGDEPS + [ "//submodules/Display", "//submodules/Postbox", "//submodules/TelegramCore", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index b2ef550c96a..2c1e90333e8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -1,3 +1,6 @@ +// MARK: Nicegram HideReactions +import NGData +// import Foundation import UIKit import Display @@ -158,7 +161,11 @@ public struct ChatMessageItemLayoutConstants { } } -public func canViewMessageReactionList(message: Message) -> Bool { +public func canViewMessageReactionList(message: Message, isInline: Bool) -> Bool { + if isInline { + return false + } + var found = false var canViewList = false for attribute in message.attributes { @@ -272,7 +279,18 @@ public func messageIsElligibleForLargeCustomEmoji(_ message: Message) -> Bool { return true } -public func canAddMessageReactions(message: Message) -> Bool { +// MARK: Nicegram HideReactions, account added +public func canAddMessageReactions(message: Message, account: Account) -> Bool { + // MARK: Nicegram HideReactions + let isTags = message.areReactionsTags( + accountPeerId: account.peerId + ) + if !isTags, VarSystemNGSettings.hideReactions { + return false + } + // + + if message.id.namespace != Namespaces.Message.Cloud { return false } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index 63ae52c794d..e1f4f5b65f8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -343,6 +343,10 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { } } } + + override public func getEffectiveAlpha() -> CGFloat { + return self.backgroundNode.alpha + } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { @@ -718,12 +722,16 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat override public func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { } + + private var currentSelectionOffset: CGFloat = 0.0 public func updateSelectionState(animated: Bool) { + let currentSelectionOffset = self.currentSelectionOffset let offset: CGFloat = self.controllerInteraction?.selectionState != nil ? 42.0 : 0.0 + self.currentSelectionOffset = offset - let previousSubnodeTransform = self.subnodeTransform - self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); + let previousSubnodeTransform = CATransform3DMakeTranslation(currentSelectionOffset, 0.0, 0.0) + self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0) if animated { self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift index ae765fab0f4..221e58614a8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift @@ -273,6 +273,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -280,7 +281,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index 2be7c5ce220..da5b0238010 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -58,11 +58,11 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } - self.interactiveImageNode.updateMessageReaction = { [weak self] message, value in + self.interactiveImageNode.updateMessageReaction = { [weak self] message, value, force in guard let strongSelf = self, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(message, value) + item.controllerInteraction.updateMessageReaction(message, value, force) } self.interactiveImageNode.activatePinch = { [weak self] sourceNode in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index 16a2bc1c900..3334f09ecac 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -1021,6 +1021,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), constrainedSize: textConstrainedSize, availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -1028,7 +1029,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift index c73151e2ecf..c3895c87eec 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift @@ -58,9 +58,11 @@ public final class MessageReactionButtonsNode: ASDisplayNode { presentationData: ChatPresentationData, presentationContext: ChatPresentationContext, availableReactions: AvailableReactions?, + savedMessageTags: SavedMessageTags?, reactions: ReactionsMessageAttribute, accountPeer: EnginePeer?, message: Message, + associatedData: ChatMessageItemAssociatedData, alignment: DisplayAlignment, constrainedWidth: CGFloat, type: DisplayType @@ -76,7 +78,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode { deselectedForeground: themeColors.reactionInactiveForeground.argb, selectedForeground: themeColors.reactionActiveForeground.argb, extractedBackground: presentationData.theme.theme.contextMenu.backgroundColor.argb, - extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedSelectedForeground: presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb, deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb, selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb ) @@ -88,7 +91,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode { deselectedForeground: themeColors.reactionInactiveForeground.argb, selectedForeground: themeColors.reactionActiveForeground.argb, extractedBackground: presentationData.theme.theme.contextMenu.backgroundColor.argb, - extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedSelectedForeground: presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb, deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb, selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb ) @@ -105,7 +109,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode { deselectedForeground: themeColors.reactionInactiveForeground.argb, selectedForeground: themeColors.reactionActiveForeground.argb, extractedBackground: presentationData.theme.theme.contextMenu.backgroundColor.argb, - extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb, + extractedSelectedForeground: presentationData.theme.theme.contextMenu.primaryColor.argb, deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb, selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb ) @@ -120,11 +125,11 @@ public final class MessageReactionButtonsNode: ASDisplayNode { let reactionButtonsResult = self.container.update( context: context, - action: { [weak self] value in - guard let strongSelf = self else { + action: { [weak self] _, value in + guard let self else { return } - strongSelf.reactionSelected?(value) + self.reactionSelected?(value) }, reactions: reactions.reactions.map { reaction in var centerAnimation: TelegramMediaFile? @@ -172,11 +177,21 @@ public final class MessageReactionButtonsNode: ASDisplayNode { } } + var title: String? + if isTag, let savedMessageTags { + for tag in savedMessageTags.tags { + if tag.reaction == reaction.value { + title = tag.title + } + } + } + return ReactionButtonsAsyncLayoutContainer.Reaction( reaction: ReactionButtonComponent.Reaction( value: reaction.value, centerAnimation: centerAnimation, - animationFileId: animationFileId + animationFileId: animationFileId, + title: title ), count: Int(reaction.count), peers: peers, @@ -335,7 +350,7 @@ public final class MessageReactionButtonsNode: ASDisplayNode { let itemValue = item.value let itemNode = item.node item.node.view.isGestureEnabled = true - let canViewReactionList = canViewMessageReactionList(message: message) + let canViewReactionList = canViewMessageReactionList(message: message, isInline: associatedData.isInline) item.node.view.activateAfterCompletion = !canViewReactionList item.node.view.activated = { [weak itemNode] gesture, _ in guard let strongSelf = self, let itemNode = itemNode else { @@ -475,7 +490,7 @@ public final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleConte guard let strongSelf = self, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } self.buttonsNode.openReactionPreview = { [weak self] gesture, sourceNode, value in @@ -511,7 +526,7 @@ public final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleConte context: item.context, presentationData: item.presentationData, presentationContext: item.controllerInteraction.presentationContext, - availableReactions: item.associatedData.availableReactions, reactions: reactionsAttribute, accountPeer: item.associatedData.accountPeer, message: item.message, alignment: .left, constrainedWidth: constrainedSize.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing) + availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, reactions: reactionsAttribute, accountPeer: item.associatedData.accountPeer, message: item.message, associatedData: item.associatedData, alignment: .left, constrainedWidth: constrainedSize.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing) return (layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + buttonsUpdate.proposedWidth, { boundingWidth in var boundingSize = CGSize() @@ -589,8 +604,10 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode { public let presentationData: ChatPresentationData public let presentationContext: ChatPresentationContext public let availableReactions: AvailableReactions? + public let savedMessageTags: SavedMessageTags? public let reactions: ReactionsMessageAttribute public let message: Message + public let associatedData: ChatMessageItemAssociatedData public let accountPeer: EnginePeer? public let isIncoming: Bool public let constrainedWidth: CGFloat @@ -600,8 +617,10 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode { presentationData: ChatPresentationData, presentationContext: ChatPresentationContext, availableReactions: AvailableReactions?, + savedMessageTags: SavedMessageTags?, reactions: ReactionsMessageAttribute, message: Message, + associatedData: ChatMessageItemAssociatedData, accountPeer: EnginePeer?, isIncoming: Bool, constrainedWidth: CGFloat @@ -610,8 +629,10 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode { self.presentationData = presentationData self.presentationContext = presentationContext self.availableReactions = availableReactions + self.savedMessageTags = savedMessageTags self.reactions = reactions self.message = message + self.associatedData = associatedData self.accountPeer = accountPeer self.isIncoming = isIncoming self.constrainedWidth = constrainedWidth @@ -648,9 +669,11 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode { presentationData: arguments.presentationData, presentationContext: arguments.presentationContext, availableReactions: arguments.availableReactions, + savedMessageTags: arguments.savedMessageTags, reactions: arguments.reactions, accountPeer: arguments.accountPeer, message: arguments.message, + associatedData: arguments.associatedData, alignment: arguments.isIncoming ? .left : .right, constrainedWidth: arguments.constrainedWidth, type: .freeform diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift index c97d3af5887..d8610b77dee 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -129,6 +129,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)), constrainedSize: textConstrainedSize, availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -136,7 +137,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD index e65d172e967..4955cb74eb2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD @@ -19,6 +19,9 @@ swift_library( "//submodules/AppBundle", "//submodules/ChatPresentationInterfaceState", "//submodules/TelegramUI/Components/Chat/ChatInputPanelNode", + "//submodules/TelegramUI/Components/EntityKeyboard", + "//submodules/TelegramUI/Components/Chat/TopMessageReactions", + "//submodules/ReactionSelectionNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift index 9c6f5d7ad95..fe85c315dfa 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift @@ -10,6 +10,56 @@ import AccountContext import AppBundle import ChatPresentationInterfaceState import ChatInputPanelNode +import ReactionSelectionNode +import EntityKeyboard +import TopMessageReactions + +private final class ChatMessageSelectionInputPanelNodeViewForOverlayContent: UIView, ChatInputPanelViewForOverlayContent { + var reactionContextNode: ReactionContextNode? + var anchorRect: CGRect? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.backgroundTapGesture(_:)))) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + @objc private func backgroundTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.dismissReactionSelection() + } + } + + func dismissReactionSelection() { + if let reactionContextNode = self.reactionContextNode { + self.reactionContextNode = nil + reactionContextNode.animateOut(to: self.anchorRect, animatingOutToReaction: false) + ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut).updateAlpha(node: reactionContextNode, alpha: 0.0, completion: { [weak reactionContextNode] _ in + reactionContextNode?.removeFromSupernode() + }) + } + } + + func maybeDismissContent(point: CGPoint) { + if self.hitTest(point, with: nil) == self { + self.dismissReactionSelection() + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let reactionContextNode = self.reactionContextNode { + if let result = reactionContextNode.view.hitTest(self.convert(point, to: reactionContextNode.view), with: event) { + return result + } + return self + } + return nil + } +} public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { private let deleteButton: HighlightableButtonNode @@ -21,8 +71,12 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { private let copyButton: HighlightableButtonNode // private let shareButton: HighlightableButtonNode + private let tagButton: HighlightableButtonNode + private let tagEditButton: HighlightableButtonNode private let separatorNode: ASDisplayNode + private let reactionOverlayContainer: ChatMessageSelectionInputPanelNodeViewForOverlayContent + private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)? private var presentationInterfaceState: ChatPresentationInterfaceState? private var actions: ChatAvailableMessageActions? @@ -35,30 +89,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { public var selectedMessages = Set() { didSet { if oldValue != self.selectedMessages { - self.forwardButton.isEnabled = self.selectedMessages.count != 0 - // MARK: Nicegram SelectedMessagesMenu - self.cloudButton.isEnabled = self.forwardButton.isEnabled - self.copyForwardButton.isEnabled = self.forwardButton.isEnabled - self.copyButton.isEnabled = self.forwardButton.isEnabled - // - - if self.selectedMessages.isEmpty { - self.actions = nil - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) - } - self.canDeleteMessagesDisposable.set(nil) - } else if let context = self.context { - self.canDeleteMessagesDisposable.set((context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: self.selectedMessages) - |> deliverOnMainQueue).startStrict(next: { [weak self] actions in - if let strongSelf = self { - strongSelf.actions = actions - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState { - let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) - } - } - })) - } + self.updateActions() } } } @@ -99,6 +130,14 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.shareButton.isAccessibilityElement = true self.shareButton.accessibilityLabel = strings.VoiceOver_MessageContextShare + self.tagButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0))) + self.tagButton.isAccessibilityElement = true + self.tagButton.accessibilityLabel = strings.VoiceOver_MessageSelectionButtonTag + + self.tagEditButton = HighlightableButtonNode(pointerStyle: .rectangle(CGSize(width: 56.0, height: 40.0))) + self.tagEditButton.isAccessibilityElement = true + self.tagEditButton.accessibilityLabel = strings.VoiceOver_MessageSelectionButtonTag + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) self.reportButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionReport"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) @@ -107,18 +146,26 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.tagButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TagIcon"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.tagEditButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TagEditIcon"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) self.separatorNode = ASDisplayNode() self.separatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor + self.reactionOverlayContainer = ChatMessageSelectionInputPanelNodeViewForOverlayContent() + super.init() self.addSubnode(self.deleteButton) self.addSubnode(self.reportButton) self.addSubnode(self.forwardButton) self.addSubnode(self.shareButton) + self.addSubnode(self.tagButton) + self.addSubnode(self.tagEditButton) self.addSubnode(self.separatorNode) + self.viewForOverlayContent = self.reactionOverlayContainer + self.forwardButton.isImplicitlyDisabled = true self.shareButton.isImplicitlyDisabled = true @@ -146,12 +193,43 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.copyForwardButton.addTarget(self, action: #selector(self.copyForwardButtonPressed), forControlEvents: .touchUpInside) self.copyButton.addTarget(self, action: #selector(self.copyButtonPressed), forControlEvents: .touchUpInside) // + + self.tagButton.addTarget(self, action: #selector(self.tagButtonPressed), forControlEvents: .touchUpInside) + self.tagEditButton.addTarget(self, action: #selector(self.tagButtonPressed), forControlEvents: .touchUpInside) } deinit { self.canDeleteMessagesDisposable.dispose() } + private func updateActions() { + self.forwardButton.isEnabled = self.selectedMessages.count != 0 + + // MARK: Nicegram SelectedMessagesMenu + self.cloudButton.isEnabled = self.forwardButton.isEnabled + self.copyForwardButton.isEnabled = self.forwardButton.isEnabled + self.copyButton.isEnabled = self.forwardButton.isEnabled + // + + if self.selectedMessages.isEmpty { + self.actions = nil + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + } + self.canDeleteMessagesDisposable.set(nil) + } else if let context = self.context { + self.canDeleteMessagesDisposable.set((context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: self.selectedMessages, keepUpdated: true) + |> deliverOnMainQueue).startStrict(next: { [weak self] actions in + if let strongSelf = self { + strongSelf.actions = actions + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState { + let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + } + } + })) + } + } + public func updateTheme(theme: PresentationTheme) { if self.theme !== theme { self.theme = theme @@ -172,6 +250,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { // self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.tagButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/WebpageIcon"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.tagEditButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/LinkSettingsIcon"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) self.separatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor } @@ -242,6 +322,120 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { } } + @objc private func tagButtonPressed() { + guard let context = self.context else { + return + } + + if self.reactionOverlayContainer.reactionContextNode != nil { + return + } + + let reactionItems: Signal<[ReactionItem], NoError> = tagMessageReactions(context: context) + + let _ = (reactionItems + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] reactionItems in + guard let self, let actions = self.actions, let context = self.context else { + return + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let reactionContextNode = ReactionContextNode( + context: context, + animationCache: context.animationCache, + presentationData: presentationData, + items: reactionItems.map(ReactionContextItem.reaction), + selectedItems: actions.editTags, + title: actions.editTags.isEmpty ? presentationData.strings.Chat_ReactionSelectionTitleAddTag : presentationData.strings.Chat_ReactionSelectionTitleEditTag, + reactionsLocked: false, + alwaysAllowPremiumReactions: false, + allPresetReactionsAreAvailable: true, + getEmojiContent: { animationCache, animationRenderer in + let mappedReactionItems: [EmojiComponentReactionItem] = reactionItems.map { reaction -> EmojiComponentReactionItem in + return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation) + } + + return EmojiPagerContentComponent.emojiInputData( + context: context, + animationCache: animationCache, + animationRenderer: animationRenderer, + isStandalone: false, + subject: .messageTag, + hasTrending: false, + topReactionItems: mappedReactionItems, + areUnicodeEmojiEnabled: false, + areCustomEmojiEnabled: true, + chatPeerId: context.account.peerId, + selectedItems: Set(), + premiumIfSavedMessages: false + ) + }, + isExpandedUpdated: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + }, + requestLayout: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + } + ) + reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in + guard let self, let context = self.context, let presentationInterfaceState = self.presentationInterfaceState, let actions = self.actions else { + return + } + + self.interfaceInteraction?.cancelMessageSelection(.animated(duration: 0.4, curve: .spring)) + + if actions.editTags.contains(updateReaction.reaction) { + var reactions = actions.editTags + reactions.remove(updateReaction.reaction) + let mappedUpdatedReactions = reactions.map { reaction -> UpdateMessageReaction in + switch reaction { + case let .builtin(value): + return .builtin(value) + case let .custom(fileId): + return .custom(fileId: fileId, file: nil) + } + } + if let selectionState = presentationInterfaceState.interfaceState.selectionState { + context.engine.messages.setMessageReactions(ids: Array(selectionState.selectedIds), reactions: mappedUpdatedReactions) + } + } else { + if let selectionState = presentationInterfaceState.interfaceState.selectionState { + context.engine.messages.addMessageReactions(ids: Array(selectionState.selectedIds), reactions: [updateReaction]) + } + } + + self.reactionOverlayContainer.dismissReactionSelection() + } + reactionContextNode.displayTail = true + reactionContextNode.forceTailToRight = true + reactionContextNode.forceDark = false + self.reactionOverlayContainer.reactionContextNode = reactionContextNode + self.reactionOverlayContainer.addSubnode(reactionContextNode) + + self.update(transition: .immediate) + }) + } + + private func update(transition: ContainedViewLayoutTransition) { + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + } + } + override public 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 { self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) @@ -274,6 +468,19 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.deleteButton.isHidden = false } self.reportButton.isHidden = !self.reportButton.isEnabled + + if actions.setTag { + if !actions.editTags.isEmpty { + self.tagButton.isHidden = true + self.tagEditButton.isHidden = false + } else { + self.tagButton.isHidden = false + self.tagEditButton.isHidden = true + } + } else { + self.tagButton.isHidden = true + self.tagEditButton.isHidden = true + } } else { self.deleteButton.isEnabled = false self.deleteButton.isHidden = self.peerMedia @@ -281,11 +488,17 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.reportButton.isHidden = true self.forwardButton.isImplicitlyDisabled = true self.shareButton.isImplicitlyDisabled = true + // MARK: Nicegram SelectedMessagesMenu self.copyForwardButton.isImplicitlyDisabled = self.forwardButton.isImplicitlyDisabled self.cloudButton.isImplicitlyDisabled = self.forwardButton.isImplicitlyDisabled self.copyButton.isImplicitlyDisabled = self.forwardButton.isImplicitlyDisabled // + + self.tagButton.isHidden = true + self.tagEditButton.isHidden = true + self.tagButton.isHidden = true + self.tagEditButton.isHidden = true } if self.reportButton.isHidden || (self.peerMedia && self.deleteButton.isHidden && self.reportButton.isHidden) { @@ -301,49 +514,82 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { width -= additionalSideInsets.right } + var tagButton: HighlightableButtonNode? + if !self.tagButton.isHidden { + tagButton = self.tagButton + } else if !self.tagEditButton.isHidden { + tagButton = self.tagEditButton + } + + // MARK: Nicegram SelectedMessagesMenu + reportButton.isHidden = true + // + + // MARK: Nicegram SelectedMessagesMenu, change let to var + var buttons: [HighlightableButtonNode] if self.reportButton.isHidden { - self.deleteButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) - self.forwardButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 57.0, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) - self.shareButton.frame = CGRect(origin: CGPoint(x: floor((width - rightInset - 57.0) / 2.0), y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) + if let tagButton { + buttons = [ + self.deleteButton, + tagButton, + self.shareButton, + self.forwardButton, + ] + } else { + buttons = [ + self.deleteButton, + self.shareButton, + self.forwardButton + ] + } } else if !self.deleteButton.isHidden { - let buttons: [HighlightableButtonNode] = [ - self.deleteButton, - self.reportButton, - self.shareButton, - self.forwardButton - ] - let buttonSize = CGSize(width: 57.0, height: panelHeight) - - let availableWidth = width - leftInset - rightInset - let spacing: CGFloat = floor((availableWidth - buttonSize.width * CGFloat(buttons.count)) / CGFloat(buttons.count - 1)) - var offset: CGFloat = leftInset - for i in 0 ..< buttons.count { - let button = buttons[i] - if i == buttons.count - 1 { - button.frame = CGRect(origin: CGPoint(x: width - rightInset - buttonSize.width, y: 0.0), size: buttonSize) - } else { - button.frame = CGRect(origin: CGPoint(x: offset, y: 0.0), size: buttonSize) - } - offset += buttonSize.width + spacing + if let tagButton { + buttons = [ + self.deleteButton, + self.reportButton, + tagButton, + self.shareButton, + self.forwardButton + ] + } else { + buttons = [ + self.deleteButton, + self.reportButton, + self.shareButton, + self.forwardButton + ] } } else { - self.deleteButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 53.0, height: panelHeight)) - self.forwardButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 57.0, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) - self.reportButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 53.0, height: 47.0)) - self.shareButton.frame = CGRect(origin: CGPoint(x: floor((width - rightInset - 57.0) / 2.0), y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) + if let tagButton { + buttons = [ + self.deleteButton, + self.reportButton, + tagButton, + self.shareButton, + self.forwardButton + ] + } else { + buttons = [ + self.deleteButton, + self.reportButton, + self.shareButton, + self.forwardButton + ] + } } - // MARK: Nicegram SelectedMessagesMenu - reportButton.isHidden = true - let buttons: [HighlightableButtonNode] = [ - self.deleteButton, - self.reportButton, - self.shareButton, + let forwardButtonIndex = buttons.firstIndex(of: self.forwardButton) + let nicegramButtonsIndex = forwardButtonIndex ?? buttons.startIndex + let nicegramButtons = [ self.cloudButton, self.copyButton, self.copyForwardButton, - self.forwardButton - ].filter { !$0.isHidden } + ] + buttons.insert( + contentsOf: nicegramButtons, + at: nicegramButtonsIndex + ) + let buttonSize = CGSize(width: 57.0, height: panelHeight) let availableWidth = width - leftInset - rightInset @@ -358,11 +604,23 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { } offset += buttonSize.width + spacing } - // transition.updateAlpha(node: self.separatorNode, alpha: isSecondary ? 1.0 : 0.0) self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: panelHeight), size: CGSize(width: width, height: UIScreenPixel)) + if let reactionContextNode = self.reactionOverlayContainer.reactionContextNode, let tagButton { + let isFirstTime = reactionContextNode.bounds.isEmpty + + let size = CGSize(width: width, height: maxHeight) + let reactionsAnchorRect = tagButton.frame.offsetBy(dx: -54.0, dy: -(panelHeight - size.height) + 14.0) + transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - size.height), size: size)) + reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, transition: transition) + reactionContextNode.updateIsIntersectingContent(isIntersectingContent: true, transition: .immediate) + if isFirstTime { + reactionContextNode.animateIn(from: reactionsAnchorRect) + } + } + return panelHeight } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 77f5d149474..9a32ef904d2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -625,6 +625,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -632,7 +633,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -838,8 +839,10 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { presentationData: item.presentationData, presentationContext: item.controllerInteraction.presentationContext, availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: reactions, message: item.message, + associatedData: item.associatedData, accountPeer: item.associatedData.accountPeer, isIncoming: item.message.effectivelyIncoming(item.context.account.peerId), constrainedWidth: maxReactionsWidth @@ -1231,7 +1234,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { guard let strongSelf = weakSelf.value, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } reactionButtonsNode.openReactionPreview = { gesture, sourceNode, value in guard let strongSelf = weakSelf.value, let item = strongSelf.item else { @@ -1329,8 +1332,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { case let .optionalAction(f): f() case let .openContextMenu(openContextMenu): - if canAddMessageReactions(message: item.message) { - item.controllerInteraction.updateMessageReaction(openContextMenu.tapMessage, .default) + // MARK: Nicegram HideReactions, account added + if canAddMessageReactions(message: item.message, account: item.context.account) { + item.controllerInteraction.updateMessageReaction(openContextMenu.tapMessage, .default, false) } else { item.controllerInteraction.openMessageContextMenu(openContextMenu.tapMessage, openContextMenu.selectAll, self, openContextMenu.subFrame, nil, nil) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index f08f74aaee7..21f93be1b7b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -570,6 +570,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { layoutInput: dateLayoutInput, constrainedSize: textConstrainedSize, availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, reactions: dateReactionsAndPeers.reactions, reactionPeers: dateReactionsAndPeers.peers, displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, @@ -577,7 +578,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { replyCount: dateReplies, isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread), hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: item.message, isInline: item.associatedData.isInline), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -714,11 +715,11 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.addSubnode(statusNode) - statusNode.reactionSelected = { [weak strongSelf] value in + statusNode.reactionSelected = { [weak strongSelf] _, value in guard let strongSelf, let item = strongSelf.item else { return } - item.controllerInteraction.updateMessageReaction(item.message, .reaction(value)) + item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false) } statusNode.openReactionPreview = { [weak strongSelf] gesture, sourceNode, value in guard let strongSelf, let item = strongSelf.item else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift index b154236175d..eb8e1107122 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift @@ -48,7 +48,7 @@ public final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleCon } else { titleColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor } - let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, nil, false, presentationData.strings.Conversation_UpdateTelegram, titleColor, false, true) + let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, nil, nil, false, presentationData.strings.Conversation_UpdateTelegram, titleColor, false, true) let initialWidth = buttonWidth + insets.left + insets.right diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 0ef76df20d6..99026d071ec 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -497,7 +497,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent for media in item.message.media { switch media { case _ as TelegramMediaImage, _ as TelegramMediaFile, _ as TelegramMediaStory: - mediaAndFlags = (media, ChatMessageAttachedContentNodeMediaFlags()) + mediaAndFlags = (media, [.preferMediaInline]) default: break } diff --git a/submodules/TelegramUI/Components/Chat/ChatNavigationButton/Sources/ChatNavigationButton.swift b/submodules/TelegramUI/Components/Chat/ChatNavigationButton/Sources/ChatNavigationButton.swift index d273af5b4ed..3eb1ce98c68 100644 --- a/submodules/TelegramUI/Components/Chat/ChatNavigationButton/Sources/ChatNavigationButton.swift +++ b/submodules/TelegramUI/Components/Chat/ChatNavigationButton/Sources/ChatNavigationButton.swift @@ -6,7 +6,7 @@ public enum ChatNavigationButtonAction: Equatable { case clearHistory case clearCache case cancelMessageSelection - case search + case search(hasTags: Bool) case dismiss case toggleInfoPanel case spacer diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index b06772c55c7..35841b1ed06 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -65,6 +65,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, setupReplyMessage: { _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in + }, cancelMessageSelection: { _ in }, deleteSelectedMessages: { }, reportSelectedMessages: { }, reportMessages: { _, _ in @@ -170,6 +171,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { }, updateHistoryFilter: { _ in + }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in }, chatController: { return nil @@ -177,7 +179,7 @@ public final class ChatRecentActionsController: TelegramBaseController { self.navigationItem.titleView = self.titleView - let rightButton = ChatNavigationButton(action: .search, buttonItem: UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.activateSearch))) + let rightButton = ChatNavigationButton(action: .search(hasTags: false), buttonItem: UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.activateSearch))) self.navigationItem.setRightBarButton(rightButton.buttonItem, animated: false) self.titleView.title = self.presentationData.strings.Channel_AdminLog_TitleAllEvents @@ -237,7 +239,7 @@ public final class ChatRecentActionsController: TelegramBaseController { self.titleView.color = self.presentationData.theme.rootController.navigationBar.primaryTextColor self.updateTitle() - let rightButton = ChatNavigationButton(action: .search, buttonItem: UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.activateSearch))) + let rightButton = ChatNavigationButton(action: .search(hasTags: false), buttonItem: UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.activateSearch))) self.navigationItem.setRightBarButton(rightButton.buttonItem, animated: false) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 7e6df9da573..788c7992570 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -210,7 +210,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } } let gallerySource = GalleryControllerItemSource.standaloneMessage(message) - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: true, reverseMessageGalleryOrder: false, navigationController: navigationController, dismissInput: { + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: true, reverseMessageGalleryOrder: false, navigationController: navigationController, dismissInput: { //self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.presentController(c, .window(.root), a) @@ -271,7 +271,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { let gesture: ContextGesture? = anyRecognizer as? ContextGesture self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, recognizer: recognizer, gesture: gesture, location: location) }, openMessageReactionContextMenu: { _, _, _, _ in - }, updateMessageReaction: { _, _ in + }, updateMessageReaction: { _, _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _, _ in }, navigateToMessageStandalone: { _ in diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index e5b988c545c..d37a02c53d2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -134,7 +134,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.titleUpdated(title: new) 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeAbout(prev, new): var peers = SimpleDictionary() var author: Peer? @@ -165,14 +165,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .content: let peers = SimpleDictionary() let attributes: [MessageAttribute] = [] let prevMessage = 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: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) 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: new, attributes: attributes, media: [], 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) + 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), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) } case let .changeUsername(prev, new): var peers = SimpleDictionary() @@ -203,7 +203,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .content: var previousAttributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = [] @@ -222,7 +222,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) 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: text, attributes: attributes, media: [], 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) + 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), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changeUsernames(prev, new): var peers = SimpleDictionary() @@ -253,7 +253,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .content: var previousAttributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = [] @@ -291,7 +291,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) 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: text, attributes: attributes, media: [], 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) + 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), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changePhoto(_, new): var peers = SimpleDictionary() @@ -310,7 +310,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.photoUpdated(image: photo) 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .toggleInvites(value): var peers = SimpleDictionary() var author: Peer? @@ -337,7 +337,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .toggleSignatures(value): var peers = SimpleDictionary() var author: Peer? @@ -364,7 +364,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .updatePinned(message): switch self.id.contentIndex { case .header: @@ -395,7 +395,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .content: if let message = message { var peers = SimpleDictionary() @@ -413,7 +413,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } 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: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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)) } else { var peers = SimpleDictionary() var author: Peer? @@ -435,7 +435,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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: 0), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .editMessage(prev, message): @@ -480,7 +480,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -497,7 +497,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } 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: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) + 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: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) } case let .deleteMessage(message): switch self.id.contentIndex { @@ -523,7 +523,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -550,7 +550,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: message.id.id), 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: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .participantJoin, .participantLeave: var peers = SimpleDictionary() @@ -568,7 +568,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { action = TelegramMediaActionType.removedMembers(peerIds: [self.entry.event.peerId]) } 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .participantInvite(participant): var peers = SimpleDictionary() var author: Peer? @@ -585,7 +585,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .participantToggleBan(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -716,7 +716,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } 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: text, attributes: attributes, media: [], 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .participantToggleAdmin(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -953,7 +953,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } 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: text, attributes: attributes, media: [], 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeStickerPack(_, new): var peers = SimpleDictionary() var author: Peer? @@ -982,7 +982,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .togglePreHistoryHidden(value): var peers = SimpleDictionary() var author: Peer? @@ -1012,7 +1012,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .updateDefaultBannedRights(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1071,7 +1071,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } 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: text, attributes: attributes, media: [], 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .pollStopped(message): switch self.id.contentIndex { case .header: @@ -1099,7 +1099,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1116,7 +1116,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } 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: message.author, text: message.text, attributes: attributes, media: message.media, 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil) + 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: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil) } case let .linkedPeerUpdated(previous, updated): var peers = SimpleDictionary() @@ -1172,7 +1172,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeGeoLocation(_, updated): var peers = SimpleDictionary() var author: Peer? @@ -1194,12 +1194,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: 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: text, attributes: [], media: [mediaMap], 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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)) } else { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .updateSlowmode(_, newValue): var peers = SimpleDictionary() @@ -1230,7 +1230,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .startGroupCall, .endGroupCall: var peers = SimpleDictionary() var author: Peer? @@ -1267,7 +1267,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .groupCallUpdateParticipantMuteStatus(participantId, isMuted): var peers = SimpleDictionary() var author: Peer? @@ -1301,7 +1301,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .updateGroupCallSettings(joinMuted): var peers = SimpleDictionary() var author: Peer? @@ -1330,7 +1330,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .groupCallUpdateParticipantVolume(participantId, volume): var peers = SimpleDictionary() var author: Peer? @@ -1361,7 +1361,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .deleteExportedInvitation(invite): var peers = SimpleDictionary() var author: Peer? @@ -1387,7 +1387,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .revokeExportedInvitation(invite): var peers = SimpleDictionary() var author: Peer? @@ -1413,7 +1413,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .editExportedInvitation(_, updatedInvite): var peers = SimpleDictionary() var author: Peer? @@ -1439,7 +1439,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .participantJoinedViaInvite(invite, joinedViaFolderLink): var peers = SimpleDictionary() var author: Peer? @@ -1470,7 +1470,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeHistoryTTL(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1501,7 +1501,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeAvailableReactions(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1572,7 +1572,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeTheme(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1603,7 +1603,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .participantJoinByRequest(invite, approvedBy): var peers = SimpleDictionary() var author: Peer? @@ -1643,7 +1643,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .toggleCopyProtection(value): var peers = SimpleDictionary() var author: Peer? @@ -1670,7 +1670,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .sendMessage(message): switch self.id.contentIndex { case .header: @@ -1695,7 +1695,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1712,7 +1712,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } 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: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .createTopic(info): var peers = SimpleDictionary() @@ -1732,7 +1732,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .deleteTopic(info): var peers = SimpleDictionary() var author: Peer? @@ -1753,7 +1753,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .editTopic(prevInfo, newInfo): var peers = SimpleDictionary() var author: Peer? @@ -1826,7 +1826,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .pinTopic(prevInfo, newInfo): var peers = SimpleDictionary() var author: Peer? @@ -1864,7 +1864,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .toggleForum(isForum): var peers = SimpleDictionary() var author: Peer? @@ -1885,7 +1885,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .toggleAntiSpam(isEnabled): var peers = SimpleDictionary() var author: Peer? @@ -1906,7 +1906,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeNameColor(_, _, updatedColor, updatedIcon): var peers = SimpleDictionary() var author: Peer? @@ -1975,7 +1975,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: TelegramMediaActionType.CustomTextAttributes(attributes: additionalAttributes)) 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeProfileColor(_, _, updatedColor, updatedIcon): var peers = SimpleDictionary() var author: Peer? @@ -2054,7 +2054,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: TelegramMediaActionType.CustomTextAttributes(attributes: additionalAttributes)) 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeStatus(_, status): var peers = SimpleDictionary() var author: Peer? @@ -2089,7 +2089,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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 .changeWallpaper(_, wallpaper): var peers = SimpleDictionary() var author: Peer? @@ -2117,7 +2117,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } 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, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + 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/Chat/ChatShareMessageTagView/BUILD b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD new file mode 100644 index 00000000000..c70d3954266 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatShareMessageTagView", + module_name = "ChatShareMessageTagView", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/UndoUI", + "//submodules/ContextUI", + "//submodules/ReactionSelectionNode", + "//submodules/TelegramUI/Components/EntityKeyboard", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/Sources/ChatShareMessageTagView.swift b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/Sources/ChatShareMessageTagView.swift new file mode 100644 index 00000000000..18e56b8cb2e --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/Sources/ChatShareMessageTagView.swift @@ -0,0 +1,157 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit +import Display +import TelegramCore +import TelegramPresentationData +import AccountContext +import UndoUI +import ReactionSelectionNode +import EntityKeyboard + +public final class ChatShareMessageTagView: UIView, UndoOverlayControllerAdditionalView { + private struct Params: Equatable { + var size: CGSize + + init(size: CGSize) { + self.size = size + } + } + + public var interaction: UndoOverlayControllerAdditionalViewInteraction? + + private var reactionContextNode: ReactionContextNode? + private var params: Params? + + public init(context: AccountContext, presentationData: PresentationData, isSingleMessage: Bool, reactionItems: [ReactionItem], completion: @escaping (TelegramMediaFile, UpdateMessageReaction) -> Void) { + super.init(frame: CGRect()) + + let reactionContextNode = ReactionContextNode( + context: context, + animationCache: context.animationCache, + presentationData: presentationData, + items: reactionItems.map(ReactionContextItem.reaction), + selectedItems: Set(), + title: isSingleMessage ? presentationData.strings.Chat_ForwardToSavedMessageTagSelectionTitle : presentationData.strings.Chat_ForwardToSavedMessagesTagSelectionTitle, + reactionsLocked: false, + alwaysAllowPremiumReactions: false, + allPresetReactionsAreAvailable: true, + getEmojiContent: { animationCache, animationRenderer in + let mappedReactionItems: [EmojiComponentReactionItem] = reactionItems.map { reaction -> EmojiComponentReactionItem in + return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation) + } + + return EmojiPagerContentComponent.emojiInputData( + context: context, + animationCache: animationCache, + animationRenderer: animationRenderer, + isStandalone: false, + subject: .messageTag, + hasTrending: false, + topReactionItems: mappedReactionItems, + areUnicodeEmojiEnabled: false, + areCustomEmojiEnabled: true, + chatPeerId: context.account.peerId, + selectedItems: Set(), + premiumIfSavedMessages: false + ) + }, + isExpandedUpdated: { [weak self] transition in + guard let self else { + return + } + self.interaction?.disableTimeout() + self.update(transition: transition) + }, + requestLayout: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + } + ) + reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in + guard let self else { + return + } + + let _ = (context.engine.stickers.availableReactions() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] availableReactions in + guard let self, let availableReactions else { + return + } + + var file: TelegramMediaFile? + switch updateReaction { + case .builtin: + for reaction in availableReactions.reactions { + if reaction.value == updateReaction.reaction { + file = reaction.centerAnimation + break + } + } + case let .custom(_, fileValue): + file = fileValue + } + + guard let file else { + return + } + + completion(file, updateReaction) + + self.interaction?.dismiss() + }) + } + reactionContextNode.displayTail = false + reactionContextNode.forceTailToRight = true + reactionContextNode.forceDark = false + self.reactionContextNode = reactionContextNode + + self.addSubnode(reactionContextNode) + } + + required public init(coder: NSCoder) { + preconditionFailure() + } + + public func update(size: CGSize, transition: ContainedViewLayoutTransition) { + let params = Params(size: size) + if self.params == params { + return + } + self.params = params + self.update(params: params, transition: transition) + } + + private func update(transition: ContainedViewLayoutTransition) { + if let params = self.params { + self.update(params: params, transition: transition) + } + } + + private func update(params: Params, transition: ContainedViewLayoutTransition) { + guard let reactionContextNode = self.reactionContextNode else { + return + } + + let isFirstTime = reactionContextNode.bounds.isEmpty + + let reactionsAnchorRect = CGRect(origin: CGPoint(x: params.size.width - 1.0, y: 0.0), size: CGSize(width: 1.0, height: 1.0)) + + transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: params.size)) + reactionContextNode.updateLayout(size: params.size, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, transition: transition) + reactionContextNode.updateIsIntersectingContent(isIntersectingContent: true, transition: .immediate) + if isFirstTime { + reactionContextNode.animateIn(from: reactionsAnchorRect) + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift index fb206e91ae7..436fe99c0b0 100644 --- a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift @@ -87,8 +87,6 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { private let messageDisposable = MetaDisposable() public let messageIds: [MessageId] private var messages: [Message] = [] - private var authors: String? - private var sourcePeer: (peerId: PeerId, displayTitle: String)? let closeButton: HighlightableButtonNode let lineNode: ASImageNode @@ -169,69 +167,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { if messages.isEmpty { strongSelf.dismiss?() } else { - var authors = "" - var uniquePeerIds = Set() - var title = "" - var text = NSMutableAttributedString(string: "") - var sourcePeer: (PeerId, String)? - for message in messages { - if let author = message.forwardInfo?.author ?? message.effectiveAuthor, !uniquePeerIds.contains(author.id) { - uniquePeerIds.insert(author.id) - if !authors.isEmpty { - authors.append(", ") - } - if author.id == context.account.peerId { - authors.append(strongSelf.strings.DialogList_You) - } else { - authors.append(EnginePeer(author).compactDisplayTitle) - } - } - if let peer = message.peers[message.id.peerId] { - sourcePeer = (peer.id, EnginePeer(peer).displayTitle(strings: strongSelf.strings, displayOrder: strongSelf.nameDisplayOrder)) - } - } - - if messages.count == 1 { - title = strongSelf.strings.Conversation_ForwardOptions_ForwardTitleSingle - let (string, entities, _) = textStringForForwardedMessage(messages[0], strings: strings) - - text = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(authors): ", font: Font.regular(15.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)) - - let additionalText = NSMutableAttributedString(attributedString: NSAttributedString(string: string, font: Font.regular(15.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)) - for entity in entities { - switch entity.type { - case let .CustomEmoji(_, fileId): - let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) - if range.lowerBound >= 0 && range.upperBound <= additionalText.length { - additionalText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: messages[0].associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile), range: range) - } - default: - break - } - } - - text.append(additionalText) - } else { - title = strongSelf.strings.Conversation_ForwardOptions_ForwardTitle(Int32(messages.count)) - text = NSMutableAttributedString(attributedString: NSAttributedString(string: strongSelf.strings.Conversation_ForwardFrom(authors).string, font: Font.regular(15.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)) - } - strongSelf.messages = messages - strongSelf.sourcePeer = sourcePeer - strongSelf.authors = authors - - strongSelf.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor) - strongSelf.textNode.attributedText = text - strongSelf.originalText = text - strongSelf.textNode.visibility = true - - let headerString: String - if messages.count == 1 { - headerString = "Forward message" - } else { - headerString = "Forward messages" - } - strongSelf.actionArea.accessibilityLabel = "\(headerString). From: \(authors).\n\(text)" if let (size, inset, interfaceState) = strongSelf.validLayout { strongSelf.updateState(size: size, inset: inset, interfaceState: interfaceState) @@ -354,6 +290,67 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { self.iconView.frame = CGRect(origin: CGPoint(x: 7.0 + inset, y: 10.0), size: icon.size) } + var authors = "" + var uniquePeerIds = Set() + var title = "" + var text = NSMutableAttributedString(string: "") + + for message in self.messages { + if let author = message.forwardInfo?.author ?? message.effectiveAuthor, !uniquePeerIds.contains(author.id) { + uniquePeerIds.insert(author.id) + if !authors.isEmpty { + authors.append(", ") + } + if author.id == context.account.peerId { + authors.append(self.strings.DialogList_You) + } else { + authors.append(EnginePeer(author).compactDisplayTitle) + } + } + } + + if self.messages.count == 1 { + title = self.strings.Conversation_ForwardOptions_ForwardTitleSingle + let (string, entities, _) = textStringForForwardedMessage(messages[0], strings: strings) + + text = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(authors): ", font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)) + + let additionalText = NSMutableAttributedString(attributedString: NSAttributedString(string: string, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)) + for entity in entities { + switch entity.type { + case let .CustomEmoji(_, fileId): + let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + if range.lowerBound >= 0 && range.upperBound <= additionalText.length { + additionalText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: messages[0].associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile), range: range) + } + default: + break + } + } + + text.append(additionalText) + } else { + title = self.strings.Conversation_ForwardOptions_ForwardTitle(Int32(messages.count)) + text = NSMutableAttributedString(attributedString: NSAttributedString(string: self.strings.Conversation_ForwardFrom(authors).string, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)) + } + + if interfaceState.interfaceState.forwardOptionsState?.hideNames == true { + text = NSMutableAttributedString(attributedString: NSAttributedString(string: self.strings.Conversation_ForwardOptions_SenderNamesRemoved, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)) + } + + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor) + self.textNode.attributedText = text + self.originalText = text + self.textNode.visibility = true + + let headerString: String + if messages.count == 1 { + headerString = "Forward message" + } else { + headerString = "Forward messages" + } + self.actionArea.accessibilityLabel = "\(headerString). From: \(authors).\n\(text)" + let titleSize = self.titleNode.updateLayout(CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset, height: bounds.size.height)) self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset, y: 7.0), size: titleSize) @@ -362,9 +359,12 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { } @objc private func closePressed() { - guard let (peerId, peerDisplayTitle) = self.sourcePeer else { + guard let message = self.messages.first, let peer = message.peers[message.id.peerId] else { return } + let peerId = peer.id + let peerDisplayTitle = EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder) + let messageCount = Int32(self.messageIds.count) let messages = self.strings.Conversation_ForwardOptions_Messages(messageCount) let string: PresentationStrings.FormattedString diff --git a/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD new file mode 100644 index 00000000000..24344ea62dc --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SavedTagNameAlertController", + module_name = "SavedTagNameAlertController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ComponentFlow", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramUI/Components/EmojiStatusComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/Sources/SavedTagNameAlertController.swift b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/Sources/SavedTagNameAlertController.swift new file mode 100644 index 00000000000..23f7b9bf182 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/Sources/SavedTagNameAlertController.swift @@ -0,0 +1,556 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import AccountContext +import ComponentFlow +import MultilineTextComponent +import BalancedTextComponent +import EmojiStatusComponent + +private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { + private var theme: PresentationTheme + private let backgroundNode: ASImageNode + private let textInputNode: EditableTextNode + private let placeholderNode: ASTextNode + private let characterLimitView = ComponentView() + + private let characterLimit: Int + + var updateHeight: (() -> Void)? + var complete: (() -> Void)? + var textChanged: ((String) -> Void)? + + private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0) + private let inputInsets: UIEdgeInsets + + var text: String { + get { + return self.textInputNode.attributedText?.string ?? "" + } + set { + self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputTextColor) + self.placeholderNode.isHidden = !newValue.isEmpty + } + } + + var placeholder: String = "" { + didSet { + self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor) + } + } + + init(theme: PresentationTheme, placeholder: String, characterLimit: Int) { + self.theme = theme + self.characterLimit = characterLimit + + self.inputInsets = UIEdgeInsets(top: 9.0, left: 17.0, bottom: 9.0, right: 16.0) + + self.backgroundNode = ASImageNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0) + + self.textInputNode = EditableTextNode() + self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(13.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor] + self.textInputNode.clipsToBounds = true + self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) + self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: self.inputInsets.left, bottom: self.inputInsets.bottom, right: self.inputInsets.right) + self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance + self.textInputNode.keyboardType = .default + self.textInputNode.autocapitalizationType = .sentences + self.textInputNode.returnKeyType = .done + self.textInputNode.autocorrectionType = .default + self.textInputNode.tintColor = theme.actionSheet.controlAccentColor + + self.placeholderNode = ASTextNode() + self.placeholderNode.isUserInteractionEnabled = false + self.placeholderNode.displaysAsynchronously = false + self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor) + + super.init() + + self.textInputNode.delegate = self + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.textInputNode) + self.addSubnode(self.placeholderNode) + } + + func updateTheme(_ theme: PresentationTheme) { + self.theme = theme + + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) + self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance + self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor) + self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor + } + + func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + let backgroundInsets = self.backgroundInsets + let inputInsets = self.inputInsets + + let textFieldHeight = self.calculateTextFieldMetrics(width: width) + let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom + + let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + + let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) + transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left + 17.0, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) + + transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height))) + + let characterLimitString: String + let characterLimitColor: UIColor + if self.text.count <= self.characterLimit { + let remaining = self.characterLimit - self.text.count + if remaining < 5 { + characterLimitString = "\(remaining)" + } else { + characterLimitString = " " + } + characterLimitColor = self.theme.list.itemPlaceholderTextColor + } else { + characterLimitString = "\(self.characterLimit - self.text.count)" + characterLimitColor = self.theme.list.itemDestructiveColor + } + + let characterLimitSize = self.characterLimitView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: characterLimitString, font: Font.regular(13.0), textColor: characterLimitColor)) + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + if let characterLimitComponentView = self.characterLimitView.view { + if characterLimitComponentView.superview == nil { + self.view.addSubview(characterLimitComponentView) + } + characterLimitComponentView.frame = CGRect(origin: CGPoint(x: width - 23.0 - characterLimitSize.width, y: 18.0), size: characterLimitSize) + } + + return panelHeight + } + + func activateInput() { + self.textInputNode.becomeFirstResponder() + } + + func deactivateInput() { + self.textInputNode.resignFirstResponder() + } + + @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { + self.updateTextNodeText(animated: true) + self.textChanged?(editableTextNode.textView.text) + self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty + } + + func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + /*let currentText = (editableTextNode.attributedText?.string ?? "") as NSString + var resultText = currentText.replacingCharacters(in: range, with: text) + if resultText.count > self.characterLimit { + resultText = String(resultText[resultText.startIndex ..< resultText.index(resultText.startIndex, offsetBy: self.characterLimit)]) + + editableTextNode.attributedText = NSAttributedString(string: resultText, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputTextColor) + return false + }*/ + + if text == "\n" { + self.complete?() + return false + } + return true + } + + private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat { + let backgroundInsets = self.backgroundInsets + let inputInsets = self.inputInsets + + let unboundTextFieldHeight = max(34.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height)) + + return min(61.0, max(34.0, unboundTextFieldHeight)) + } + + private func updateTextNodeText(animated: Bool) { + let backgroundInsets = self.backgroundInsets + + let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width) + + let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom + if !self.bounds.size.height.isEqual(to: panelHeight) { + self.updateHeight?() + } + } + + @objc func clearPressed() { + self.textInputNode.attributedText = nil + self.deactivateInput() + } +} + +private final class SavedTagNameAlertContentNode: AlertContentNode { + private let context: AccountContext + private var theme: AlertControllerTheme + private let strings: PresentationStrings + private let text: String + private let subtext: String + private let titleFont: PromptControllerTitleFont + private let reaction: MessageReaction.Reaction + private let file: TelegramMediaFile + + private let textView = ComponentView() + private let subtextView = ComponentView() + private let iconView = ComponentView() + + let inputFieldNode: PromptInputFieldNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private let disposable = MetaDisposable() + + private var validLayout: CGSize? + + private let hapticFeedback = HapticFeedback() + + var complete: (() -> Void)? { + didSet { + self.inputFieldNode.complete = self.complete + } + } + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, subtext: String, reaction: MessageReaction.Reaction, file: TelegramMediaFile, titleFont: PromptControllerTitleFont, value: String?, characterLimit: Int) { + self.context = context + self.theme = theme + self.strings = strings + self.text = text + self.subtext = subtext + self.reaction = reaction + self.file = file + self.titleFont = titleFont + + self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: strings.Chat_EditTagTitle_Placeholder, characterLimit: characterLimit) + self.inputFieldNode.text = value ?? "" + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.inputFieldNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + self.actionNodes.last?.actionEnabled = true + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.inputFieldNode.updateHeight = { [weak self] in + if let strongSelf = self { + if let _ = strongSelf.validLayout { + strongSelf.requestLayout?(.immediate) + } + } + } + + self.inputFieldNode.textChanged = { [weak self] text in + if let strongSelf = self, let lastNode = strongSelf.actionNodes.last { + lastNode.actionEnabled = text.count <= characterLimit + strongSelf.requestLayout?(.immediate) + } + } + + self.updateTheme(theme) + } + + deinit { + self.disposable.dispose() + } + + var value: String { + return self.inputFieldNode.text + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.theme = theme + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) + + let hadValidLayout = self.validLayout != nil + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 16.0) + let spacing: CGFloat = 5.0 + let subtextSpacing: CGFloat = -1.0 + + let textSize = self.textView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: self.text, font: Font.semibold(17.0), textColor: self.theme.primaryColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: CGSize(width: measureSize.width, height: 1000.0) + ) + let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) * 0.5), y: origin.y), size: textSize) + if let textComponentView = self.textView.view { + if textComponentView.superview == nil { + self.view.addSubview(textComponentView) + } + textComponentView.frame = textFrame + } + origin.y += textSize.height + 6.0 + subtextSpacing + + let subtextSize = self.subtextView.update( + transition: .immediate, + component: AnyComponent(BalancedTextComponent( + text: .plain(NSAttributedString(string: self.subtext, font: Font.regular(13.0), textColor: self.theme.primaryColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: CGSize(width: measureSize.width, height: 1000.0) + ) + let subtextFrame = CGRect(origin: CGPoint(x: floor((size.width - subtextSize.width) * 0.5), y: origin.y), size: subtextSize) + if let subtextComponentView = self.subtextView.view { + if subtextComponentView.superview == nil { + self.view.addSubview(subtextComponentView) + } + subtextComponentView.frame = subtextFrame + } + origin.y += subtextSize.height + 6.0 + spacing + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) + + var contentWidth = max(textSize.width, minActionsWidth) + contentWidth = max(subtextSize.width, minActionsWidth) + contentWidth = max(contentWidth, 234.0) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let resultWidth = contentWidth + insets.left + insets.right + + let inputFieldWidth = resultWidth + let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) + let inputHeight = inputFieldHeight + let inputFieldFrame = CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight) + transition.updateFrame(node: self.inputFieldNode, frame: inputFieldFrame) + transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) + + let emojiSize = CGSize(width: 20.0, height: 20.0) + var visibleEmojiSize = emojiSize + if case .builtin = self.reaction { + visibleEmojiSize = CGSize(width: visibleEmojiSize.width * 2.0, height: visibleEmojiSize.height * 2.0) + } + let _ = self.iconView.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: self.context, + animationCache: self.context.animationCache, + animationRenderer: self.context.animationRenderer, + content: .animation( + content: .file(file: self.file), + size: visibleEmojiSize, + placeholderColor: self.theme.primaryColor.withMultipliedAlpha(0.2), + themeColor: self.theme.primaryColor, + loopMode: .forever + ), + isVisibleForAnimations: false, + useSharedAnimation: true, + action: nil, + emojiFileUpdated: nil + )), + environment: {}, + containerSize: visibleEmojiSize + ) + if let iconComponentView = self.iconView.view { + if iconComponentView.superview == nil { + self.view.addSubview(iconComponentView) + } + let emojiFrame = CGRect(origin: CGPoint(x: inputFieldFrame.minX + 26.0, y: inputFieldFrame.minY + 14.0), size: emojiSize) + iconComponentView.frame = visibleEmojiSize.centered(around: emojiFrame.center) + } + + let resultSize = CGSize(width: resultWidth, height: textSize.height + subtextSpacing + subtextSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom) + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + if !hadValidLayout { + self.inputFieldNode.activateInput() + } + + return resultSize + } + + func animateError() { + self.inputFieldNode.layer.addShakeAnimation() + self.hapticFeedback.error() + } +} + +public enum PromptControllerTitleFont { + case regular + case bold +} + +public func savedTagNameAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, subtext: String, titleFont: PromptControllerTitleFont = .regular, value: String?, reaction: MessageReaction.Reaction, file: TelegramMediaFile, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController { + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + + var dismissImpl: ((Bool) -> Void)? + var applyImpl: (() -> Void)? + + let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + apply(nil) + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: { + dismissImpl?(true) + applyImpl?() + })] + + let contentNode = SavedTagNameAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, subtext: subtext, reaction: reaction, file: file, titleFont: titleFont, value: value, characterLimit: characterLimit) + contentNode.complete = { + applyImpl?() + } + applyImpl = { [weak contentNode] in + guard let contentNode = contentNode else { + return + } + apply(contentNode.value) + } + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in + controller?.theme = AlertControllerTheme(presentationData: presentationData) + contentNode?.inputFieldNode.updateTheme(presentationData.theme) + }) + controller.dismissed = { _ in + presentationDataDisposable.dispose() + } + dismissImpl = { [weak controller] animated in + contentNode.inputFieldNode.deactivateInput() + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} diff --git a/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD b/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD new file mode 100644 index 00000000000..554275c6a93 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "TopMessageReactions", + module_name = "TopMessageReactions", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/AccountContext", + "//submodules/ReactionSelectionNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/TopMessageReactions.swift b/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift similarity index 59% rename from submodules/TelegramUI/Sources/TopMessageReactions.swift rename to submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift index 2dceebf1549..dddf483d4d9 100644 --- a/submodules/TelegramUI/Sources/TopMessageReactions.swift +++ b/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift @@ -5,7 +5,139 @@ import Postbox import AccountContext import ReactionSelectionNode -func topMessageReactions(context: AccountContext, message: Message) -> Signal<[ReactionItem], NoError> { +public enum AllowedReactions { + case set(Set) + case all +} + +public func peerMessageAllowedReactions(context: AccountContext, message: Message) -> Signal { + if message.id.peerId == context.account.peerId { + return .single(.all) + } + + if message.containsSecretMedia { + return .single(AllowedReactions.set(Set())) + } + + return combineLatest( + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId), + TelegramEngine.EngineData.Item.Peer.AllowedReactions(id: message.id.peerId) + ), + context.engine.stickers.availableReactions() |> take(1) + ) + |> map { data, availableReactions -> AllowedReactions? in + let (peer, allowedReactions) = data + + if let effectiveReactions = message.effectiveReactions(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)), effectiveReactions.count >= 11 { + return .set(Set(effectiveReactions.map(\.value))) + } + + switch allowedReactions { + case .unknown: + if case let .channel(channel) = peer, case .broadcast = channel.info { + if let availableReactions = availableReactions { + return .set(Set(availableReactions.reactions.map(\.value))) + } else { + return .set(Set()) + } + } + return .all + case let .known(value): + switch value { + case .all: + if case let .channel(channel) = peer, case .broadcast = channel.info { + if let availableReactions = availableReactions { + return .set(Set(availableReactions.reactions.map(\.value))) + } else { + return .set(Set()) + } + } + return .all + case let .limited(reactions): + return .set(Set(reactions)) + case .empty: + return .set(Set()) + } + } + } +} + +public func tagMessageReactions(context: AccountContext) -> Signal<[ReactionItem], NoError> { + return combineLatest( + context.engine.stickers.availableReactions(), + context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudDefaultTagReactions], namespaces: [ItemCollectionId.Namespace.max - 1], aroundIndex: nil, count: 10000000) + ) + |> take(1) + |> map { availableReactions, view -> [ReactionItem] in + var defaultTagReactions: OrderedItemListView? + for orderedView in view.orderedItemListsViews { + if orderedView.collectionId == Namespaces.OrderedItemList.CloudDefaultTagReactions { + defaultTagReactions = orderedView + } + } + + var result: [ReactionItem] = [] + var existingIds = Set() + + if let defaultTagReactions { + for item in defaultTagReactions.items { + guard let topReaction = item.contents.get(RecentReactionItem.self) else { + continue + } + switch topReaction.content { + case let .builtin(value): + if let reaction = availableReactions?.reactions.first(where: { $0.value == .builtin(value) }) { + guard let centerAnimation = reaction.centerAnimation else { + continue + } + guard let aroundAnimation = reaction.aroundAnimation else { + continue + } + + if existingIds.contains(reaction.value) { + continue + } + existingIds.insert(reaction.value) + + result.append(ReactionItem( + reaction: ReactionItem.Reaction(rawValue: reaction.value), + appearAnimation: reaction.appearAnimation, + stillAnimation: reaction.selectAnimation, + listAnimation: centerAnimation, + largeListAnimation: reaction.activateAnimation, + applicationAnimation: aroundAnimation, + largeApplicationAnimation: reaction.effectAnimation, + isCustom: false + )) + } else { + continue + } + case let .custom(file): + if existingIds.contains(.custom(file.fileId.id)) { + continue + } + existingIds.insert(.custom(file.fileId.id)) + + result.append(ReactionItem( + reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)), + appearAnimation: file, + stillAnimation: file, + listAnimation: file, + largeListAnimation: file, + applicationAnimation: nil, + largeApplicationAnimation: nil, + isCustom: true + )) + } + } + } + + return result + } +} + +public func topMessageReactions(context: AccountContext, message: Message) -> Signal<[ReactionItem], NoError> { if message.id.peerId == context.account.peerId { var loadTags = false if let effectiveReactionsAttribute = message.effectiveReactionsAttribute(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)) { @@ -19,82 +151,8 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R loadTags = true } - if "".isEmpty { - loadTags = false - } - if loadTags { - return combineLatest( - context.engine.stickers.availableReactions(), - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudDefaultTagReactions], namespaces: [ItemCollectionId.Namespace.max - 1], aroundIndex: nil, count: 10000000) - ) - |> take(1) - |> map { availableReactions, view -> [ReactionItem] in - var defaultTagReactions: OrderedItemListView? - for orderedView in view.orderedItemListsViews { - if orderedView.collectionId == Namespaces.OrderedItemList.CloudDefaultTagReactions { - defaultTagReactions = orderedView - } - } - - var result: [ReactionItem] = [] - var existingIds = Set() - - if let defaultTagReactions { - for item in defaultTagReactions.items { - guard let topReaction = item.contents.get(RecentReactionItem.self) else { - continue - } - switch topReaction.content { - case let .builtin(value): - if let reaction = availableReactions?.reactions.first(where: { $0.value == .builtin(value) }) { - guard let centerAnimation = reaction.centerAnimation else { - continue - } - guard let aroundAnimation = reaction.aroundAnimation else { - continue - } - - if existingIds.contains(reaction.value) { - continue - } - existingIds.insert(reaction.value) - - result.append(ReactionItem( - reaction: ReactionItem.Reaction(rawValue: reaction.value), - appearAnimation: reaction.appearAnimation, - stillAnimation: reaction.selectAnimation, - listAnimation: centerAnimation, - largeListAnimation: reaction.activateAnimation, - applicationAnimation: aroundAnimation, - largeApplicationAnimation: reaction.effectAnimation, - isCustom: false - )) - } else { - continue - } - case let .custom(file): - if existingIds.contains(.custom(file.fileId.id)) { - continue - } - existingIds.insert(.custom(file.fileId.id)) - - result.append(ReactionItem( - reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)), - appearAnimation: file, - stillAnimation: file, - listAnimation: file, - largeListAnimation: file, - applicationAnimation: nil, - largeApplicationAnimation: nil, - isCustom: true - )) - } - } - } - - return result - } + return tagMessageReactions(context: context) } } diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index f8b682d32d1..649a3f553eb 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -148,7 +148,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void public let openPeerMention: (String, Promise?) -> Void public let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void - public let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void + public let updateMessageReaction: (Message, ChatControllerInteractionReaction, Bool) -> Void public let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void public let activateMessagePinch: (PinchSourceContainerNode) -> Void public let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void @@ -274,7 +274,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol openPeerMention: @escaping (String, Promise?) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void, openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void, - updateMessageReaction: @escaping (Message, ChatControllerInteractionReaction) -> Void, + updateMessageReaction: @escaping (Message, ChatControllerInteractionReaction, Bool) -> Void, activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId, NavigateToMessageParams) -> Void, diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index 2674eb490d9..39c7d4f6c04 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -197,6 +197,33 @@ private func generatePeerNameColorImage(nameColor: PeerNameColors.Colors, isDark } public final class InlineStickerItemLayer: MultiAnimationRenderTarget { + private final class Arguments { + let context: InlineStickerItemLayer.Context + let userLocation: MediaResourceUserLocation + let emoji: ChatTextInputTextCustomEmojiAttribute + let cache: AnimationCache + let renderer: MultiAnimationRenderer + let unique: Bool + let placeholderColor: UIColor + let loopCount: Int? + + let pointSize: CGSize + let pixelSize: CGSize + + init(context: InlineStickerItemLayer.Context, userLocation: MediaResourceUserLocation, emoji: ChatTextInputTextCustomEmojiAttribute, cache: AnimationCache, renderer: MultiAnimationRenderer, unique: Bool, placeholderColor: UIColor, loopCount: Int?, pointSize: CGSize, pixelSize: CGSize) { + self.context = context + self.userLocation = userLocation + self.emoji = emoji + self.cache = cache + self.renderer = renderer + self.unique = unique + self.placeholderColor = placeholderColor + self.loopCount = loopCount + self.pointSize = pointSize + self.pixelSize = pixelSize + } + } + public enum Context: Equatable { public final class Custom: Equatable { public let postbox: Postbox @@ -277,17 +304,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } } - private let context: InlineStickerItemLayer.Context - private let userLocation: MediaResourceUserLocation - private let emoji: ChatTextInputTextCustomEmojiAttribute - private let cache: AnimationCache - private let renderer: MultiAnimationRenderer - private let unique: Bool - private let placeholderColor: UIColor - private let loopCount: Int? - - private let pointSize: CGSize - private let pixelSize: CGSize + private let arguments: Arguments? private var isDisplayingPlaceholder: Bool = false private var didProcessTintColor: Bool = false @@ -353,19 +370,22 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } public init(context: InlineStickerItemLayer.Context, userLocation: MediaResourceUserLocation, attemptSynchronousLoad: Bool, emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, cache: AnimationCache, renderer: MultiAnimationRenderer, unique: Bool = false, placeholderColor: UIColor, pointSize: CGSize, dynamicColor: UIColor? = nil, loopCount: Int? = nil) { - self.context = context - self.userLocation = userLocation - self.emoji = emoji - self.cache = cache - self.renderer = renderer - self.unique = unique - self.placeholderColor = placeholderColor - self._dynamicColor = dynamicColor - self.loopCount = loopCount - let scale = min(2.0, UIScreenScale) - self.pointSize = pointSize - self.pixelSize = CGSize(width: self.pointSize.width * scale, height: self.pointSize.height * scale) + + self.arguments = Arguments( + context: context, + userLocation: userLocation, + emoji: emoji, + cache: cache, + renderer: renderer, + unique: unique, + placeholderColor: placeholderColor, + loopCount: loopCount, + pointSize: pointSize, + pixelSize: CGSize(width: pointSize.width * scale, height: pointSize.height * scale) + ) + + self._dynamicColor = dynamicColor super.init() @@ -392,7 +412,9 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } override public init(layer: Any) { - preconditionFailure() + self.arguments = nil + + super.init(layer: layer) } required public init?(coder: NSCoder) { @@ -479,9 +501,13 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } private func updatePlayback() { + guard let arguments = self.arguments else { + return + } + var shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations - if shouldBePlaying, let loopCount = self.loopCount, self.currentLoopCount >= loopCount { + if shouldBePlaying, let loopCount = arguments.loopCount, self.currentLoopCount >= loopCount { shouldBePlaying = false } @@ -549,6 +575,10 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } private func updateFile(file: TelegramMediaFile, attemptSynchronousLoad: Bool) { + guard let arguments = self.arguments else { + return + } + if self.file?.fileId == file.fileId { return } @@ -556,8 +586,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { self.file = file if attemptSynchronousLoad { - if !self.renderer.loadFirstFrameSynchronously(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize) { - if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: self.pointSize, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: self.placeholderColor) { + if !arguments.renderer.loadFirstFrameSynchronously(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, size: arguments.pixelSize) { + if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: arguments.pointSize, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: arguments.placeholderColor) { self.contents = image.cgImage self.isDisplayingPlaceholder = true self.updateTintColor() @@ -570,10 +600,10 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } else { let isTemplate = file.isCustomTemplateEmoji - let pointSize = self.pointSize - let placeholderColor = self.placeholderColor + let pointSize = arguments.pointSize + let placeholderColor = arguments.placeholderColor let isThumbnailCancelled = Atomic(value: false) - self.loadDisposable = self.renderer.loadFirstFrame(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: animationCacheFetchFile(postbox: self.context.postbox, userLocation: self.userLocation, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: true, customColor: isTemplate ? .white : nil), completion: { [weak self] result, isFinal in + self.loadDisposable = arguments.renderer.loadFirstFrame(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, size: arguments.pixelSize, fetch: animationCacheFetchFile(postbox: arguments.context.postbox, userLocation: arguments.userLocation, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: true, customColor: isTemplate ? .white : nil), completion: { [weak self] result, isFinal in if !result { MultiAnimationRendererImpl.firstFrameQueue.async { let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: pointSize, scale: min(2.0, UIScreenScale), imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor) @@ -605,19 +635,23 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } private func loadAnimation() { + guard let arguments = self.arguments else { + return + } + guard let file = self.file else { return } let isTemplate = file.isCustomTemplateEmoji - let context = self.context + let context = arguments.context if file.isAnimatedSticker || file.isVideoSticker || file.isVideoEmoji { - let keyframeOnly = self.pixelSize.width >= 120.0 + let keyframeOnly = arguments.pixelSize.width >= 120.0 - self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, unique: self.unique, size: self.pixelSize, fetch: animationCacheFetchFile(postbox: self.context.postbox, userLocation: self.userLocation, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: keyframeOnly, customColor: isTemplate ? .white : nil)) + self.disposable = arguments.renderer.add(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, unique: arguments.unique, size: arguments.pixelSize, fetch: animationCacheFetchFile(postbox: arguments.context.postbox, userLocation: arguments.userLocation, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: keyframeOnly, customColor: isTemplate ? .white : nil)) } else { - self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, unique: self.unique, size: self.pixelSize, fetch: { options in + self.disposable = arguments.renderer.add(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, unique: arguments.unique, size: arguments.pixelSize, fetch: { options in let dataDisposable = context.postbox.mediaBox.resourceData(file.resource).start(next: { result in guard result.complete else { return @@ -626,7 +660,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { cacheStillSticker(path: result.path, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, customColor: isTemplate ? .white : nil) }) - let fetchDisposable = freeMediaFileResourceInteractiveFetched(postbox: context.postbox, userLocation: self.userLocation, fileReference: .customEmoji(media: file), resource: file.resource).start() + let fetchDisposable = freeMediaFileResourceInteractiveFetched(postbox: context.postbox, userLocation: arguments.userLocation, fileReference: .customEmoji(media: file), resource: file.resource).start() return ActionDisposable { dataDisposable.dispose() @@ -645,6 +679,10 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } override public func transitionToContents(_ contents: AnyObject, didLoop: Bool) { + guard let arguments = self.arguments else { + return + } + if self.isDisplayingPlaceholder { self.isDisplayingPlaceholder = false self.updateTintColor() @@ -675,7 +713,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { if didLoop { self.currentLoopCount += 1 - if let loopCount = self.loopCount, self.currentLoopCount >= loopCount { + if let loopCount = arguments.loopCount, self.currentLoopCount >= loopCount { self.updatePlayback() } } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 98c3fb677c2..5139ba06d42 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1116,7 +1116,7 @@ public extension EmojiPagerContentComponent { } } - let hasRecentEmoji = ![.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .profilePhoto, .groupPhoto, .topicIcon, .backgroundIcon, .reactionList].contains(subject) + let hasRecentEmoji = ![.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .profilePhoto, .groupPhoto, .topicIcon, .backgroundIcon, .reactionList, .messageTag].contains(subject) if let recentEmoji = recentEmoji, hasRecentEmoji { for item in recentEmoji.items { @@ -1381,7 +1381,7 @@ public extension EmojiPagerContentComponent { var displaySearchWithPlaceholder: String? let searchInitiallyHidden = true if hasSearch { - if [.reaction(onlyTop: false), .quickReaction].contains(subject) { + if [.reaction(onlyTop: false), .quickReaction, .messageTag].contains(subject) { displaySearchWithPlaceholder = strings.EmojiSearch_SearchReactionsPlaceholder } else if case .status = subject { displaySearchWithPlaceholder = strings.EmojiSearch_SearchStatusesPlaceholder @@ -1436,7 +1436,7 @@ public extension EmojiPagerContentComponent { ) } - let warpContentsOnEdges = [.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .channelStatus, .profilePhoto, .groupPhoto, .backgroundIcon].contains(subject) + let warpContentsOnEdges = [.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .channelStatus, .profilePhoto, .groupPhoto, .backgroundIcon, .messageTag].contains(subject) let enableLongPress = [.reaction(onlyTop: true), .reaction(onlyTop: false), .status, .channelStatus].contains(subject) return EmojiPagerContentComponent( diff --git a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift index 89afc9f9d3d..e559c37311b 100644 --- a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift +++ b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift @@ -52,10 +52,10 @@ public final class MoreHeaderButton: HighlightableButtonNode { strongSelf.contextAction?(strongSelf.containerNode, gesture) } - self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0)) + self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 30.0, height: 44.0)) self.referenceNode.frame = self.containerNode.bounds - self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color) + //self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color) if let image = self.iconNode.image { self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) } @@ -72,11 +72,13 @@ public final class MoreHeaderButton: HighlightableButtonNode { private var content: Content? public func setContent(_ content: Content, animated: Bool = false) { if case .more = content { - let animationSize = CGSize(width: 22.0, height: 22.0) + let animationSize = CGSize(width: 30.0, height: 30.0) let _ = self.animationView.update( transition: .immediate, component: AnyComponent(LottieComponent( - content: LottieComponent.AppBundleContent(name: "anim_profilemore"), + content: LottieComponent.AppBundleContent( + name: "anim_moredots" + ), color: self.color )), environment: {}, @@ -119,13 +121,13 @@ public final class MoreHeaderButton: HighlightableButtonNode { if let animationComponentView = self.animationView.view { animationComponentView.isHidden = true } - case let .more(image): - if let image = image { + case .more: + /*if let image = image { self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) } self.iconNode.image = image - self.iconNode.isHidden = false + self.iconNode.isHidden = false*/ if let animationComponentView = self.animationView.view { animationComponentView.isHidden = false } @@ -143,13 +145,13 @@ public final class MoreHeaderButton: HighlightableButtonNode { if let animationComponentView = self.animationView.view { animationComponentView.isHidden = true } - case let .more(image): - if let image = image { + case .more: + /*if let image = image { self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) } self.iconNode.image = image - self.iconNode.isHidden = false + self.iconNode.isHidden = false*/ if let animationComponentView = self.animationView.view { animationComponentView.isHidden = false } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift index ad0b9b11d13..80df1b1ffdb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift @@ -17,6 +17,83 @@ import ChatListUI import DeleteChatPeerActionSheetItem import UndoUI +private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNodeNavigationContentNode { + private struct Params: Equatable { + var width: CGFloat + var defaultHeight: CGFloat + var insets: UIEdgeInsets + + init(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets) { + self.width = width + self.defaultHeight = defaultHeight + self.insets = insets + } + } + + weak var chatController: ChatController? + let contentNode: NavigationBarContentNode + + var panelNode: ChatControllerCustomNavigationPanelNode? + private var appliedPanelNode: ChatControllerCustomNavigationPanelNode? + + private var params: Params? + + init(chatController: ChatController, contentNode: NavigationBarContentNode) { + self.chatController = chatController + self.contentNode = contentNode + + super.init() + + self.addSubnode(self.contentNode) + } + + func update(transition: ContainedViewLayoutTransition) { + if let params = self.params { + let _ = self.update(width: params.width, defaultHeight: params.defaultHeight, insets: params.insets, transition: transition) + } + } + + func update(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat { + self.params = Params(width: width, defaultHeight: defaultHeight, insets: insets) + + let size = CGSize(width: width, height: defaultHeight) + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: size)) + self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) + + var contentHeight: CGFloat = size.height + 10.0 + + if self.appliedPanelNode !== self.panelNode { + if let previous = self.appliedPanelNode { + transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in + previous?.removeFromSupernode() + }) + } + + self.appliedPanelNode = self.panelNode + if let panelNode = self.panelNode, let chatController = self.chatController { + self.addSubnode(panelNode) + let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: .immediate, chatController: chatController) + let panelHeight = panelLayout.backgroundHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) + panelNode.frame = panelFrame + panelNode.alpha = 0.0 + transition.updateAlpha(node: panelNode, alpha: 1.0) + + contentHeight += panelHeight - 1.0 + } + } else if let panelNode = self.panelNode, let chatController = self.chatController { + let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: transition, chatController: chatController) + let panelHeight = panelLayout.backgroundHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) + transition.updateFrame(node: panelNode, frame: panelFrame) + + contentHeight += panelHeight - 1.0 + } + + return contentHeight + } +} + public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private let context: AccountContext @@ -24,7 +101,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI public weak var parentController: ViewController? - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -50,6 +127,16 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI private var emptyShimmerEffectNode: ChatListShimmerNode? private var shimmerNodeOffset: CGFloat = 0.0 private var floatingHeaderOffset: CGFloat? + + private let coveringView: UIView + private var chatController: ChatController? + private var removeChatWhenNotSearching: Bool = false + + private var searchNavigationContentNode: SearchNavigationContentNode? + public var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? { + return self.searchNavigationContentNode + } + public var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)? public init(context: AccountContext, navigationController: @escaping () -> NavigationController?) { self.context = context @@ -58,6 +145,8 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI self.presentationData = presentationData let strings = presentationData.strings + self.coveringView = UIView() + self.chatListNode = ChatListNode( context: self.context, location: .savedMessagesChats, @@ -79,11 +168,16 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI autoSetReady: false, isMainTab: nil ) + self.chatListNode.synchronousDrawingWhenNotAnimated = true super.init() + self.clipsToBounds = true + self.addSubnode(self.chatListNode) + self.view.addSubview(self.coveringView) + self.presentationDataDisposable = (self.context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in guard let self else { @@ -278,7 +372,12 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI chatController.canReadHistory.set(false) let source: ContextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: parentController.navigationController as? NavigationController)) - let contextController = ContextController(presentationData: self.presentationData, source: source, items: savedMessagesPeerMenuItems(context: self.context, threadId: threadId, parentController: parentController) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = ContextController(presentationData: self.presentationData, source: source, items: savedMessagesPeerMenuItems(context: self.context, threadId: threadId, parentController: parentController, deletePeerChat: { [weak self] peerId in + guard let self else { + return + } + self.chatListNode.deletePeerChat?(peerId, false) + }) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) parentController.presentInGlobalOverlay(contextController) } } @@ -287,12 +386,89 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI deinit { self.presentationDataDisposable?.dispose() } + + public func activateSearch() { + if self.chatController == nil { + let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: self.context.account.peerId), subject: nil, botStart: nil, mode: .standard(.embedded(invertDirection: false))) + chatController.alwaysShowSearchResultsAsList = true + + self.chatController = chatController + chatController.navigation_setNavigationController(self.navigationController()) + + self.insertSubnode(chatController.displayNode, aboveSubnode: self.chatListNode) + chatController.displayNode.alpha = 0.0 + chatController.displayNode.clipsToBounds = true + + self.updateChatController(transition: .immediate) + + let _ = (chatController.ready.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self, weak chatController] _ in + guard let self, let chatController, self.chatController === chatController else { + return + } + + chatController.stateUpdated = { [weak self] transition in + guard let self, let chatController = self.chatController else { + return + } + if let contentNode = chatController.customNavigationBarContentNode { + self.removeChatWhenNotSearching = true + + chatController.displayNode.layer.allowsGroupOpacity = true + if transition.isAnimated { + Transition.easeInOut(duration: 0.2).setAlpha(layer: chatController.displayNode.layer, alpha: 1.0) + } + + if self.searchNavigationContentNode?.contentNode !== contentNode { + self.searchNavigationContentNode = SearchNavigationContentNode(chatController: chatController, contentNode: contentNode) + self.searchNavigationContentNode?.panelNode = chatController.customNavigationPanelNode + self.externalDataUpdated?(transition) + } else if self.searchNavigationContentNode?.panelNode !== chatController.customNavigationPanelNode { + self.searchNavigationContentNode?.panelNode = chatController.customNavigationPanelNode + self.externalDataUpdated?(transition.isAnimated ? transition : .animated(duration: 0.4, curve: .spring)) + } else { + self.searchNavigationContentNode?.update(transition: transition) + } + } else { + if self.searchNavigationContentNode !== nil { + self.searchNavigationContentNode = nil + self.externalDataUpdated?(transition) + } + + if self.removeChatWhenNotSearching { + self.removeChatController() + } + } + } + + chatController.activateSearch(domain: .everything, query: "") + }) + } + } + + private func removeChatController() { + if let chatController = self.chatController { + self.chatController = nil + + let displayNode = chatController.displayNode + chatController.displayNode.layer.allowsGroupOpacity = true + chatController.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak displayNode] _ in + displayNode?.removeFromSupernode() + }) + } + } public func ensureMessageIsVisible(id: MessageId) { } public func scrollToTop() -> Bool { - self.chatListNode.scrollToPosition(.top(adjustForTempInset: false)) + if let chatController = self.chatController { + let _ = chatController.performScrollToTop() + } else { + self.chatListNode.scrollToPosition(.top(adjustForTempInset: false)) + } return false } @@ -350,25 +526,67 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI public func updateSelectedMessages(animated: Bool) { } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + private func updateChatController(transition: ContainedViewLayoutTransition) { + guard let chatController = self.chatController else { + return + } + guard let currentParams = self.currentParams else { + return + } + + let size = currentParams.size + let topInset = currentParams.topInset + let sideInset = currentParams.sideInset + let bottomInset = currentParams.bottomInset + let navigationHeight = currentParams.navigationHeight + let deviceMetrics = currentParams.deviceMetrics + let isScrollingLockedAtTop = currentParams.isScrollingLockedAtTop + + let fullHeight = navigationHeight + size.height + + let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: -navigationHeight), size: CGSize(width: size.width, height: fullHeight)) - transition.updateFrame(node: self.chatListNode, frame: CGRect(origin: CGPoint(), size: size)) + if !chatController.displayNode.bounds.isEmpty { + if let contextController = chatController.visibleContextController as? ContextController { + let deltaY = chatFrame.minY - chatController.displayNode.frame.minY + contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -deltaY * 0.0), transition: transition) + } + } + + let combinedBottomInset = bottomInset + transition.updateFrame(node: chatController.displayNode, frame: chatFrame) + chatController.updateIsScrollingLockedAtTop(isScrollingLockedAtTop: isScrollingLockedAtTop) + chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: topInset + navigationHeight, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: navigationHeight + topInset + 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) + } + + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics: deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) + + self.coveringView.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor + transition.updateFrame(view: self.coveringView, frame: CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: size.width, height: topInset + 1.0))) + + let fullHeight = navigationHeight + size.height + let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: -navigationHeight), size: CGSize(width: size.width, height: fullHeight)) + let combinedBottomInset = bottomInset + + transition.updateFrame(node: self.chatListNode, frame: chatFrame) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) self.chatListNode.updateLayout( transition: transition, updateSizeAndInsets: ListViewUpdateSizeAndInsets( size: size, - insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), + insets: UIEdgeInsets(top: topInset + navigationHeight, left: sideInset, bottom: combinedBottomInset, right: sideInset), duration: duration, curve: curve ), - visibleTopInset: topInset, - originalTopInset: topInset, + visibleTopInset: topInset + navigationHeight, + originalTopInset: topInset + navigationHeight, storiesInset: 0.0, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0 ) + + self.updateChatController(transition: transition) } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD index 6ff7241b519..067d9150189 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/ComponentFlow", "//submodules/AppBundle", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode", + "//submodules/ContextUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift index 89d0a2a7f09..2acf7e00724 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift @@ -10,6 +10,84 @@ import ComponentFlow import TelegramUIPreferences import AppBundle import PeerInfoPaneNode +import ContextUI + +private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNodeNavigationContentNode { + private struct Params: Equatable { + var width: CGFloat + var defaultHeight: CGFloat + var insets: UIEdgeInsets + + init(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets) { + self.width = width + self.defaultHeight = defaultHeight + self.insets = insets + } + } + + weak var chatController: ChatController? + let contentNode: NavigationBarContentNode + + var panelNode: ChatControllerCustomNavigationPanelNode? + private var appliedPanelNode: ChatControllerCustomNavigationPanelNode? + + private var params: Params? + + init(chatController: ChatController, contentNode: NavigationBarContentNode) { + self.chatController = chatController + self.contentNode = contentNode + + super.init() + + self.addSubnode(self.contentNode) + } + + func update(transition: ContainedViewLayoutTransition) { + if let params = self.params { + let _ = self.update(width: params.width, defaultHeight: params.defaultHeight, insets: params.insets, transition: transition) + } + } + + func update(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat { + self.params = Params(width: width, defaultHeight: defaultHeight, insets: insets) + + let size = CGSize(width: width, height: defaultHeight) + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: size)) + self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) + + var contentHeight: CGFloat = size.height + 10.0 + + if self.appliedPanelNode !== self.panelNode { + if let previous = self.appliedPanelNode { + transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in + previous?.removeFromSupernode() + }) + } + + self.appliedPanelNode = self.panelNode + if let panelNode = self.panelNode, let chatController = self.chatController { + self.addSubnode(panelNode) + let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: .immediate, chatController: chatController) + let panelHeight = panelLayout.backgroundHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) + panelNode.frame = panelFrame + panelNode.alpha = 0.0 + transition.updateAlpha(node: panelNode, alpha: 1.0) + + contentHeight += panelHeight - 1.0 + } + } else if let panelNode = self.panelNode, let chatController = self.chatController { + let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: transition, chatController: chatController) + let panelHeight = panelLayout.backgroundHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) + transition.updateFrame(node: panelNode, frame: panelFrame) + + contentHeight += panelHeight - 1.0 + } + + return contentHeight + } +} public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private let context: AccountContext @@ -18,6 +96,8 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro private let chatController: ChatController + private let coveringView: UIView + public weak var parentController: ViewController? { didSet { if self.parentController !== oldValue { @@ -51,6 +131,12 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro public var tabBarOffset: CGFloat { return 0.0 } + + private var searchNavigationContentNode: SearchNavigationContentNode? + public var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? { + return self.searchNavigationContentNode + } + public var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)? private var presentationData: PresentationData private var presentationDataDisposable: Disposable? @@ -61,10 +147,15 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.navigationController = navigationController self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.coveringView = UIView() + self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .replyThread(message: ChatReplyThreadMessage(peerId: context.account.peerId, threadId: peerId.toInt64(), channelMessageId: nil, isChannelPost: false, isForumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)), subject: nil, botStart: nil, mode: .standard(.embedded(invertDirection: true))) + self.chatController.navigation_setNavigationController(navigationController()) super.init() + self.clipsToBounds = true + self.presentationDataDisposable = (self.context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in guard let self else { @@ -73,10 +164,47 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.presentationData = presentationData }) + let strings = self.presentationData.strings + self.statusPromise.set(self.context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: self.context.account.peerId, threadId: peerId.toInt64(), tag: []) + ) + |> map { count in + if let count { + return PeerInfoStatusData(text: strings.Conversation_Messages(Int32(count)), isActivity: false, key: .savedMessages) + } else { + return nil + } + }) + self.ready.set(self.chatController.ready.get()) self.addSubnode(self.chatController.displayNode) self.chatController.displayNode.clipsToBounds = true + + self.view.addSubview(self.coveringView) + + self.chatController.stateUpdated = { [weak self] transition in + guard let self else { + return + } + if let contentNode = self.chatController.customNavigationBarContentNode { + if self.searchNavigationContentNode?.contentNode !== contentNode { + self.searchNavigationContentNode = SearchNavigationContentNode(chatController: self.chatController, contentNode: contentNode) + self.searchNavigationContentNode?.panelNode = self.chatController.customNavigationPanelNode + self.externalDataUpdated?(transition) + } else if self.searchNavigationContentNode?.panelNode !== self.chatController.customNavigationPanelNode { + self.searchNavigationContentNode?.panelNode = self.chatController.customNavigationPanelNode + self.externalDataUpdated?(transition.isAnimated ? transition : .animated(duration: 0.4, curve: .spring)) + } else { + self.searchNavigationContentNode?.update(transition: transition) + } + } else { + if self.searchNavigationContentNode !== nil { + self.searchNavigationContentNode = nil + self.externalDataUpdated?(transition) + } + } + } } deinit { @@ -124,6 +252,9 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro super.didLoad() } + public func activateSearch() { + self.chatController.activateSearch(domain: .everything, query: "") + } override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return true @@ -141,14 +272,27 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro public func updateSelectedMessages(animated: Bool) { } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) - let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height - topInset)) + + let fullHeight = navigationHeight + size.height + + let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: -navigationHeight), size: CGSize(width: size.width, height: fullHeight)) + + if !self.chatController.displayNode.bounds.isEmpty { + if let contextController = self.chatController.visibleContextController as? ContextController { + let deltaY = chatFrame.minY - self.chatController.displayNode.frame.minY + contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -deltaY * 0.0), transition: transition) + } + } + + self.coveringView.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor + transition.updateFrame(view: self.coveringView, frame: CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: size.width, height: topInset + 1.0))) let combinedBottomInset = bottomInset transition.updateFrame(node: self.chatController.displayNode, frame: chatFrame) self.chatController.updateIsScrollingLockedAtTop(isScrollingLockedAtTop: isScrollingLockedAtTop) - self.chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) + self.chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: topInset + navigationHeight, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: navigationHeight + topInset + 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift index 2cd86e88ac9..328d0612e59 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift @@ -9,6 +9,8 @@ public enum PeerInfoPaneKey: Int32 { case members case stories case media + case savedMessagesChats + case savedMessages case files case music case voice @@ -16,8 +18,6 @@ public enum PeerInfoPaneKey: Int32 { case gifs case groupsInCommon case recommended - case savedMessagesChats - case savedMessages } public struct PeerInfoStatusData: Equatable { @@ -39,6 +39,10 @@ public struct PeerInfoStatusData: Equatable { } } +public protocol PeerInfoPanelNodeNavigationContentNode: ASDisplayNode { + func update(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat +} + public protocol PeerInfoPaneNode: ASDisplayNode { var isReady: Signal { get } @@ -48,7 +52,10 @@ public protocol PeerInfoPaneNode: ASDisplayNode { var tabBarOffsetUpdated: ((ContainedViewLayoutTransition) -> Void)? { get set } var tabBarOffset: CGFloat { get } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) + var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? { get } + var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)? { get set } + + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) func scrollToTop() -> Bool func transferVelocity(_ velocity: CGFloat) func cancelPreviewGestures() @@ -59,3 +66,15 @@ public protocol PeerInfoPaneNode: ASDisplayNode { func updateSelectedMessages(animated: Bool) func ensureMessageIsVisible(id: MessageId) } + +public extension PeerInfoPaneNode { + var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? { + return nil + } + var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)? { + get { + return nil + } set(value) { + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index fd690648f86..0e7ef333136 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -148,6 +148,7 @@ swift_library( "//submodules/MediaPickerUI", "//submodules/AttachmentUI", "//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent", + "//submodules/Components/MultilineTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift index 1c8448b3122..054cfeba82a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift @@ -786,7 +786,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe return self._itemInteraction! } - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -959,8 +959,8 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe let wasFirstHistoryView = self.isFirstHistoryView self.isFirstHistoryView = false - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate) + if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { + self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate) if !self.didSetReady { self.didSetReady = true self.ready.set(.single(true)) @@ -1066,9 +1066,9 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let previousParams = self.currentParams - self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height - topInset))) @@ -1110,7 +1110,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe private var previousDidScrollTimestamp: Double = 0.0 func scrollViewDidScroll(_ scrollView: UIScrollView) { - if let (size, _, sideInset, bottomInset, _, visibleHeight, _, _, presentationData) = self.currentParams { + if let (size, _, sideInset, bottomInset, _, visibleHeight, _, _, _, presentationData) = self.currentParams { self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: false) if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0, let currentView = self.currentView, currentView.earlierId != nil { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift index 1ffa847f015..c09a19046e3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -155,7 +155,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.currentParams == nil self.currentParams = (size, isScrollingLockedAtTop, presentationData) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift index 4be4c987bb5..15be3b375fa 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift @@ -31,7 +31,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { private let listNode: ChatHistoryListNode - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -146,8 +146,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { strongSelf.playlistLocation = nil } - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring)) } } }) @@ -200,8 +200,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) var topPanelHeight: CGFloat = 0.0 if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift index 712656f0e09..cc18a4165d2 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift @@ -238,7 +238,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.currentParams == nil self.currentParams = (size, isScrollingLockedAtTop) self.presentationDataPromise.set(.single(presentationData)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift index 089c3bea7f8..991453a8178 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift @@ -195,7 +195,7 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.currentParams == nil self.currentParams = (size, sideInset, bottomInset, isScrollingLockedAtTop, presentationData) self.presentationDataPromise.set(.single(presentationData)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index c1378f36c4b..e21de0f5c7f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -210,6 +210,7 @@ final class PeerInfoScreenData { let appConfiguration: AppConfiguration? let isPowerSavingEnabled: Bool? let accountIsPremium: Bool + let hasSavedMessageTags: Bool let _isContact: Bool var forceIsContact: Bool = false @@ -245,7 +246,8 @@ final class PeerInfoScreenData { threadData: MessageHistoryThreadData?, appConfiguration: AppConfiguration?, isPowerSavingEnabled: Bool?, - accountIsPremium: Bool + accountIsPremium: Bool, + hasSavedMessageTags: Bool ) { self.peer = peer self.chatPeer = chatPeer @@ -270,6 +272,7 @@ final class PeerInfoScreenData { self.appConfiguration = appConfiguration self.isPowerSavingEnabled = isPowerSavingEnabled self.accountIsPremium = accountIsPremium + self.hasSavedMessageTags = hasSavedMessageTags } } @@ -301,10 +304,7 @@ public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: Peer let hasSavedMessagesChats: Signal if peerId == context.account.peerId { - hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead() - |> map { headPeerId -> Bool in - return headPeerId != nil - } + hasSavedMessagesChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved() |> distinctUntilChanged } else { hasSavedMessagesChats = .single(false) @@ -668,7 +668,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, threadData: nil, appConfiguration: appConfiguration, isPowerSavingEnabled: isPowerSavingEnabled, - accountIsPremium: peer?.isPremium ?? false + accountIsPremium: peer?.isPremium ?? false, + hasSavedMessageTags: false ) } } @@ -703,7 +704,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen threadData: nil, appConfiguration: nil, isPowerSavingEnabled: nil, - accountIsPremium: false + accountIsPremium: false, + hasSavedMessageTags: false )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -845,16 +847,62 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } |> distinctUntilChanged - hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead() - |> map { headPeerId -> Bool in - return headPeerId != nil + if peerId == context.account.peerId { + hasSavedMessagesChats = combineLatest( + context.engine.messages.savedMessagesHasPeersOtherThanSaved(), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.DisplaySavedChatsAsTopics() + ) + ) + |> map { hasChats, displayAsTopics -> Bool in + return hasChats || displayAsTopics + } + |> distinctUntilChanged + } else { + hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead() + |> map { headPeerId -> Bool in + return headPeerId != nil + } + |> distinctUntilChanged } - |> distinctUntilChanged } else { hasSavedMessages = .single(false) hasSavedMessagesChats = .single(false) } + let hasSavedMessageTags: Signal + if let peerId = chatLocation.peerId { + if case .peer = chatLocation { + if peerId != context.account.peerId { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64()) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } else { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: nil) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } + } else { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64()) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } + } else { + hasSavedMessageTags = .single(false) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), @@ -865,9 +913,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, - hasSavedMessages + hasSavedMessages, + hasSavedMessageTags ) - |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags -> PeerInfoScreenData in var availablePanes = availablePanes if let hasStories { @@ -926,7 +975,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen threadData: nil, appConfiguration: nil, isPowerSavingEnabled: nil, - accountIsPremium: accountIsPremium + accountIsPremium: accountIsPremium, + hasSavedMessageTags: hasSavedMessageTags ) } case .channel: @@ -987,6 +1037,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessagesChats = .single(false) } + let hasSavedMessageTags: Signal + if let peerId = chatLocation.peerId { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64()) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } else { + hasSavedMessageTags = .single(false) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), @@ -1000,9 +1063,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium, context.engine.peers.recommendedChannels(peerId: peerId), hasSavedMessages, - hasSavedMessagesChats + hasSavedMessagesChats, + hasSavedMessageTags ) - |> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> PeerInfoScreenData in var availablePanes = availablePanes if let hasStories { if hasStories { @@ -1074,7 +1138,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen threadData: nil, appConfiguration: nil, isPowerSavingEnabled: nil, - accountIsPremium: accountIsPremium + accountIsPremium: accountIsPremium, + hasSavedMessageTags: hasSavedMessageTags ) } case let .group(groupId): @@ -1221,6 +1286,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessagesChats = .single(false) } + let hasSavedMessageTags: Signal + if let peerId = chatLocation.peerId { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64()) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } else { + hasSavedMessageTags = .single(false) + } + return combineLatest(queue: .mainQueue(), context.account.viewTracker.peerView(groupId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), @@ -1235,9 +1313,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]), accountIsPremium, hasSavedMessages, - hasSavedMessagesChats + hasSavedMessagesChats, + hasSavedMessageTags ) - |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats -> Signal in + |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> Signal in var discussionPeer: Peer? if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer @@ -1322,7 +1401,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen threadData: threadData, appConfiguration: appConfiguration, isPowerSavingEnabled: nil, - accountIsPremium: accountIsPremium + accountIsPremium: accountIsPremium, + hasSavedMessageTags: hasSavedMessageTags )) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift index 566be835237..1d2a7e3d41a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift @@ -178,7 +178,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { 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.updateTintColor(view: self.iconNode.view, color: self.contentsColor) transition.updateStrokeColor(layer: self.backIconLayer, strokeColor: self.contentsColor) switch self.key { @@ -247,9 +247,17 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { case .search: text = "" accessibilityText = presentationData.strings.Common_Search - icon = nil// PresentationResourcesRootController.navigationCompactSearchIcon(presentationData.theme) + icon = nil isAnimation = true animationState = .search + case .standaloneSearch: + text = "" + accessibilityText = presentationData.strings.Common_Search + icon = PresentationResourcesRootController.navigationCompactSearchWhiteIcon(presentationData.theme) + case .searchWithTags: + text = "" + accessibilityText = presentationData.strings.Common_Search + icon = PresentationResourcesRootController.navigationCompactTagsSearchWhiteIcon(presentationData.theme) case .editPhoto: text = presentationData.strings.Settings_EditPhoto accessibilityText = text @@ -283,7 +291,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { 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) + transition.updateTintColor(view: self.iconNode.view, color: self.contentsColor) if isAnimation { self.iconNode.isHidden = true diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift index 7d6e435dfaa..cc71f68e122 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift @@ -13,6 +13,8 @@ enum PeerInfoHeaderNavigationButtonKey { case select case selectionDone case search + case searchWithTags + case standaloneSearch case editPhoto case editVideo case more @@ -51,11 +53,11 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { } for (_, button) in self.rightButtonNodes { button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition) - transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: canBeExpanded ? 8.0 : 0.0, y: 0.0)) + transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: canBeExpanded ? 16.0 : 0.0, y: 0.0)) } } - func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, shouldAnimateIn: Bool, transition: ContainedViewLayoutTransition) { let sideInset: CGFloat = 24.0 let maximumExpandOffset: CGFloat = 14.0 @@ -200,7 +202,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { if case .postStory = spec.key { buttonFrame.origin.x -= 12.0 } - nextButtonOrigin -= buttonSize.width + 4.0 + nextButtonOrigin -= buttonSize.width + 15.0 if spec.isForExpandedView { nextExpandedButtonOrigin = nextButtonOrigin } else { @@ -210,15 +212,17 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { if wasAdded { buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: self.canBeExpanded, transition: .immediate) - if key == .moreToSearch { - buttonNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) + if shouldAnimateIn { + if key == .moreToSearch || key == .searchWithTags || key == .standaloneSearch { + buttonNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) + } } buttonNode.frame = buttonFrame buttonNode.alpha = 0.0 transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) - transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? 8.0 : 0.0, y: 0.0)) + transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? 16.0 : 0.0, y: 0.0)) } else { transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) @@ -236,7 +240,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { } for key in removeKeys { if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) { - if key == .moreToSearch { + if key == .moreToSearch || key == .searchWithTags || key == .standaloneSearch { buttonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in buttonNode?.removeFromSupernode() }) @@ -263,7 +267,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { if case .postStory = spec.key { buttonFrame.origin.x -= 12.0 } - nextButtonOrigin -= buttonSize.width + 4.0 + nextButtonOrigin -= buttonSize.width + 15.0 if spec.isForExpandedView { nextExpandedButtonOrigin = nextButtonOrigin } else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index f66dcf8539e..159af9aaf8e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -40,6 +40,7 @@ import ChatAvatarNavigationNode import MultiScaleTextNode import PeerInfoCoverComponent import PeerInfoPaneNode +import MultilineTextComponent final class PeerInfoHeaderNavigationTransition { let sourceNavigationBar: NavigationBar @@ -111,6 +112,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let titleNodeContainer: ASDisplayNode let titleNodeRawContainer: ASDisplayNode let titleNode: MultiScaleTextNode + var standardTitle: ComponentView? let titleCredibilityIconView: ComponentHostView var credibilityIconSize: CGSize? @@ -172,6 +174,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { var emojiStatusPackDisposable = MetaDisposable() var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>() + var customNavigationContentNode: PeerInfoPanelNodeNavigationContentNode? + private var appliedCustomNavigationContentNode: PeerInfoPanelNodeNavigationContentNode? + private var validLayout: (width: CGFloat, deviceMetrics: DeviceMetrics)? init(context: AccountContext, controller: PeerInfoScreenImpl, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) { @@ -454,6 +459,24 @@ final class PeerInfoHeaderNode: ASDisplayNode { private var currentPanelStatusData: PeerInfoStatusData? func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat { + if self.appliedCustomNavigationContentNode !== self.customNavigationContentNode { + if let previous = self.appliedCustomNavigationContentNode { + transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in + previous?.removeFromSupernode() + }) + } + + self.appliedCustomNavigationContentNode = self.customNavigationContentNode + if let customNavigationContentNode = self.customNavigationContentNode { + self.addSubnode(customNavigationContentNode) + customNavigationContentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight)) + customNavigationContentNode.alpha = 0.0 + transition.updateAlpha(node: customNavigationContentNode, alpha: 1.0) + } + } else if let customNavigationContentNode = self.customNavigationContentNode { + transition.updateFrame(node: customNavigationContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight))) + } + var threadData = threadData if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId { threadData = nil @@ -492,7 +515,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { let credibilityIcon: CredibilityIcon var verifiedIcon: CredibilityIcon = .none if let peer = peer { - if peer.isFake { + if peer.id == self.context.account.peerId && !self.isSettings { + credibilityIcon = .none + } else if peer.isFake { credibilityIcon = .fake } else if peer.isScam { credibilityIcon = .scam @@ -517,7 +542,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { isForum = true } - self.regularContentNode.alpha = state.isEditing ? 0.0 : 1.0 + transition.updateAlpha(node: self.regularContentNode, alpha: (state.isEditing || self.customNavigationContentNode != nil) ? 0.0 : 1.0) + transition.updateAlpha(node: self.navigationButtonContainer, alpha: self.customNavigationContentNode != nil ? 0.0 : 1.0) + self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0 let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, isModalOverlay: isModalOverlay, peer: state.isEditing ? peer : nil, threadData: threadData, chatLocation: self.chatLocation, cachedData: cachedData, isContact: isContact, isSettings: isSettings, presentationData: presentationData, transition: transition) @@ -969,10 +996,17 @@ final class PeerInfoHeaderNode: ASDisplayNode { let titleShadowColor: UIColor? = nil + var displayStandardTitle = false + if let peer = peer { var title: String if peer.id == self.context.account.peerId && !self.isSettings { - title = presentationData.strings.Conversation_SavedMessages + if case .replyThread = self.chatLocation { + title = presentationData.strings.Conversation_MyNotes + } else { + displayStandardTitle = true + title = presentationData.strings.Conversation_SavedMessages + } } else if peer.id.isAnonymousSavedMessages { title = presentationData.strings.ChatList_AuthorHidden } else if let threadData = threadData { @@ -1368,6 +1402,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { if self.navigationTransition == nil && !self.isSettings && effectiveSeparatorAlpha == 1.0 && secondarySeparatorAlpha < 1.0 { effectiveSeparatorAlpha = secondarySeparatorAlpha } + if self.customNavigationContentNode != nil { + effectiveSeparatorAlpha = 0.0 + } transition.updateAlpha(node: self.separatorNode, alpha: effectiveSeparatorAlpha) self.titleNode.update(stateFractions: [ @@ -1769,6 +1806,41 @@ final class PeerInfoHeaderNode: ASDisplayNode { } } + if displayStandardTitle { + self.titleNode.isHidden = true + + let standardTitle: ComponentView + if let current = self.standardTitle { + standardTitle = current + } else { + standardTitle = ComponentView() + self.standardTitle = standardTitle + } + + let titleSize = standardTitle.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: titleStringText, font: Font.semibold(17.0), textColor: navigationContentsPrimaryColor)) + )), + environment: {}, + containerSize: CGSize(width: width, height: navigationHeight) + ) + if let standardTitleView = standardTitle.view { + if standardTitleView.superview == nil { + self.regularContentNode.view.addSubview(standardTitleView) + } + let standardTitleFrame = titleSize.centered(in: self.titleNodeContainer.frame).offsetBy(dx: 2.0, dy: 0.0) + standardTitleView.frame = standardTitleFrame + } + } else { + if let standardTitle = self.standardTitle { + self.standardTitle = nil + standardTitle.view?.removeFromSuperview() + + self.titleNode.isHidden = false + } + } + let buttonsTransitionDistance: CGFloat = -min(0.0, apparentBackgroundHeight - backgroundHeight) let buttonsTransitionDistanceNorm: CGFloat = 40.0 @@ -1990,6 +2062,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } transition.updateFrame(node: self.regularContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: resolvedHeight))) + transition.updateFrameAdditive(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentBackgroundHeight - backgroundHeight), size: CGSize(width: width, height: 1000.0))) navigationTransition.updateAlpha(node: self.buttonsContainerNode, alpha: backgroundBannerAlpha) @@ -2084,6 +2157,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { return nil } + if let customNavigationContentNode = self.customNavigationContentNode { + if let result = customNavigationContentNode.view.hitTest(self.view.convert(point, to: customNavigationContentNode.view), with: event) { + return result + } + return self.view + } + let setByFrame = self.avatarListNode.listContainerNode.setByYouNode.view.convert(self.avatarListNode.listContainerNode.setByYouNode.bounds, to: self.view).insetBy(dx: -44.0, dy: 0.0) if self.avatarListNode.listContainerNode.setByYouNode.alpha > 0.0, setByFrame.contains(point) { return self.avatarListNode.listContainerNode.setByYouNode.view diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index b24aa644b61..ce1c371908b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -18,21 +18,21 @@ final class PeerInfoPaneWrapper { let key: PeerInfoPaneKey let node: PeerInfoPaneNode var isAnimatingOut: Bool = false - private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, DeviceMetrics, CGFloat, Bool, CGFloat, PresentationData)? + private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, DeviceMetrics, CGFloat, Bool, CGFloat, CGFloat, PresentationData)? init(key: PeerInfoPaneKey, node: PeerInfoPaneNode) { self.key = key self.node = node } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - if let (currentSize, currentTopInset, currentSideInset, currentBottomInset, _, currentVisibleHeight, currentIsScrollingLockedAtTop, currentExpandProgress, currentPresentationData) = self.appliedParams { - if currentSize == size && currentTopInset == topInset, currentSideInset == sideInset && currentBottomInset == bottomInset && currentVisibleHeight == visibleHeight && currentIsScrollingLockedAtTop == isScrollingLockedAtTop && currentExpandProgress == expandProgress && currentPresentationData === presentationData { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + if let (currentSize, currentTopInset, currentSideInset, currentBottomInset, _, currentVisibleHeight, currentIsScrollingLockedAtTop, currentExpandProgress, currentNavigationHeight, currentPresentationData) = self.appliedParams { + if currentSize == size && currentTopInset == topInset, currentSideInset == sideInset && currentBottomInset == bottomInset && currentVisibleHeight == visibleHeight && currentIsScrollingLockedAtTop == isScrollingLockedAtTop && currentExpandProgress == expandProgress && currentNavigationHeight == navigationHeight && currentPresentationData === presentationData { return } } - self.appliedParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) - self.node.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: synchronous, transition: transition) + self.appliedParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) + self.node.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: transition) } } @@ -366,7 +366,8 @@ private final class PeerInfoPendingPane { parentController: ViewController?, openMediaCalendar: @escaping () -> Void, paneDidScroll: @escaping () -> Void, - ensureRectVisible: @escaping (UIView, CGRect) -> Void + ensureRectVisible: @escaping (UIView, CGRect) -> Void, + externalDataUpdated: @escaping (ContainedViewLayoutTransition) -> Void ) { let captureProtected = data.peer?.isCopyProtectionEnabled ?? false let paneNode: PeerInfoPaneNode @@ -425,6 +426,7 @@ private final class PeerInfoPendingPane { case .savedMessages: paneNode = PeerInfoChatPaneNode(context: context, peerId: peerId, navigationController: chatControllerInteraction.navigationController) } + paneNode.externalDataUpdated = externalDataUpdated paneNode.parentController = parentController self.pane = PeerInfoPaneWrapper(key: key, node: paneNode) self.disposable = (paneNode.isReady @@ -458,7 +460,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat let isReady = Promise() var didSetIsReady = false - private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?)? + private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, navigationHeight: CGFloat)? private(set) var currentPaneKey: PeerInfoPaneKey? var pendingSwitchToPaneKey: PeerInfoPaneKey? @@ -495,6 +497,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat var currentPaneUpdated: ((Bool) -> Void)? var requestExpandTabs: (() -> Bool)? + var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? var openMediaCalendar: (() -> Void)? var paneDidScroll: (() -> Void)? @@ -550,8 +553,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat if strongSelf.currentPanes[key] != nil { strongSelf.currentPaneKey = key - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.currentPaneUpdated?(true) @@ -563,8 +566,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat strongSelf.pendingSwitchToPaneKey = key strongSelf.expandOnSwitch = true - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) } } } @@ -586,6 +589,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat } return [.leftCenter, .rightCenter] } + if strongSelf.currentPane?.node.navigationContentNode != nil { + return [] + } if index == 0 { return .left } @@ -629,7 +635,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat cancelContextGestures(view: self.view) case .changed: - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { let translation = recognizer.translation(in: self.view) var transitionFraction = translation.x / size.width if currentIndex <= 0 { @@ -644,11 +650,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat // print(transitionFraction) self.paneTransitionPromise.set(transitionFraction) - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .immediate) + self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: .immediate) self.currentPaneUpdated?(false) } case .cancelled, .ended: - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { let translation = recognizer.translation(in: self.view) let velocity = recognizer.velocity(in: self.view) var directionIsToRight: Bool? @@ -672,7 +678,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat } } self.transitionFraction = 0.0 - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.35, curve: .spring)) + self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: .animated(duration: 0.35, curve: .spring)) self.currentPaneUpdated?(false) self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil)) @@ -711,7 +717,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat } } - func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { let previousAvailablePanes = self.currentAvailablePanes let availablePanes = data?.availablePanes ?? [] self.currentAvailablePanes = data?.availablePanes @@ -755,7 +761,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat currentIndex = nil } - self.currentParams = (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) + self.currentParams = (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) transition.updateAlpha(node: self.coveringBackgroundNode, alpha: expansionFraction) @@ -770,6 +776,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat let isScrollingLockedAtTop = expansionFraction < 1.0 - CGFloat.ulpOfOne let tabsHeight: CGFloat = 48.0 + let effectiveTabsHeight: CGFloat = areTabsHidden ? 0.0 : tabsHeight let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) @@ -825,12 +832,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat guard let strongSelf = self else { return } - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams { + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = strongSelf.currentParams { var transition: ContainedViewLayoutTransition = .immediate if strongSelf.pendingSwitchToPaneKey == key && strongSelf.currentPaneKey != nil { transition = .animated(duration: 0.4, curve: .spring) } - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: transition) + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: transition) } } if leftScope { @@ -849,18 +856,24 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat return } self.ensurePaneRectVisible?(self.view, sourceView.convert(rect, to: self.view)) + }, + externalDataUpdated: { [weak self] transition in + guard let self else { + return + } + self.requestUpdate?(transition) } ) self.pendingPanes[key] = pane pane.pane.node.frame = paneFrame - pane.pane.update(size: paneFrame.size, topInset: tabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, presentationData: presentationData, synchronous: true, transition: .immediate) + pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: .immediate) let paneNode = pane.pane.node pane.pane.node.tabBarOffsetUpdated = { [weak self, weak paneNode] transition in guard let strongSelf = self, let paneNode = paneNode, let currentPane = strongSelf.currentPane, paneNode === currentPane.node else { return } - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: transition) + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: transition) } } leftScope = true @@ -869,7 +882,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat for (key, pane) in self.pendingPanes { pane.pane.node.frame = paneFrame - pane.pane.update(size: paneFrame.size, topInset: tabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate) + pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate) if pane.isReady { self.pendingPanes.removeValue(forKey: key) @@ -930,7 +943,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat return } pane.isAnimatingOut = false - if let (_, _, _, _, _, _, _, data) = strongSelf.currentParams { + if let (_, _, _, _, _, _, _, data, _, _) = strongSelf.currentParams { if let availablePanes = data?.availablePanes, let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), abs(paneIndex - currentIndex) <= 1 { } else { if let pane = strongSelf.currentPanes.removeValue(forKey: key) { @@ -961,7 +974,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat paneCompletion() }) } - pane.update(size: paneFrame.size, topInset: tabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) + pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) } } @@ -973,7 +986,14 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat if isScrollingLockedAtTop || self.isMediaOnly { tabsOffset = 0.0 } - var tabsAlpha = 1.0 - tabsOffset / tabsHeight + + var tabsAlpha: CGFloat + if areTabsHidden { + tabsAlpha = 0.0 + tabsOffset = tabsHeight + } else { + tabsAlpha = 1.0 - tabsOffset / tabsHeight + } tabsAlpha *= tabsAlpha transition.updateFrame(node: self.tabsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -tabsOffset), size: CGSize(width: size.width, height: tabsHeight))) transition.updateAlpha(node: self.tabsContainerNode, alpha: tabsAlpha) @@ -1019,7 +1039,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat for (_, pane) in self.pendingPanes { let paneTransition: ContainedViewLayoutTransition = .immediate paneTransition.updateFrame(node: pane.pane.node, frame: paneFrame) - pane.pane.update(size: paneFrame.size, topInset: tabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, presentationData: presentationData, synchronous: true, transition: paneTransition) + pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: paneTransition) } var removeKeys: [PeerInfoPaneKey] = [] diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 83deaebbefa..2ea9149bbdf 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -114,6 +114,8 @@ import PeerInfoPaneNode import MediaPickerUI import AttachmentUI import BoostLevelIconComponent +import PeerInfoChatPaneNode +import PeerInfoChatListPaneNode public enum PeerInfoAvatarEditingMode { case generic @@ -318,6 +320,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, setupReplyMessage: { _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in + }, cancelMessageSelection: { _ in }, deleteSelectedMessages: { deleteMessages() }, reportSelectedMessages: { @@ -428,6 +431,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { }, updateHistoryFilter: { _ in + }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in }, chatController: { return nil @@ -2703,7 +2707,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let gesture: ContextGesture? = anyRecognizer as? ContextGesture - let _ = (strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) + let _ = (strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id], keepUpdated: false) |> deliverOnMainQueue).startStandalone(next: { actions in guard let strongSelf = self else { return @@ -2852,7 +2856,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.controller?.window?.presentInGlobalOverlay(controller) }) }, openMessageReactionContextMenu: { _, _, _, _ in - }, updateMessageReaction: { _, _ in + }, updateMessageReaction: { _, _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { [weak self] message, node, rect, gesture in guard let strongSelf = self else { @@ -2860,7 +2864,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } - let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, chatLocation: .peer(id: message.id.peerId), chatLocationContextHolder: Atomic(value: nil), message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.controller?.navigationController as? NavigationController) + let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, chatLocation: .peer(id: message.id.peerId), chatFilterTag: nil, chatLocationContextHolder: Atomic(value: nil), message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.controller?.navigationController as? NavigationController) |> deliverOnMainQueue).startStandalone(next: { previewData in guard let strongSelf = self else { gesture?.cancel() @@ -2869,7 +2873,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if let previewData = previewData { let context = strongSelf.context let strings = strongSelf.presentationData.strings - let items = strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) + let items = strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id], keepUpdated: false) |> map { actions -> [ContextMenuItem] in var items: [ContextMenuItem] = [] @@ -3317,6 +3321,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } + self.paneContainerNode.requestUpdate = { [weak self] transition in + guard let self else { + return + } + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition, additive: false) + } + } + self.paneContainerNode.ensurePaneRectVisible = { [weak self] sourceView, rect in guard let self else { return @@ -3920,12 +3933,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } strongSelf.chatInterfaceInteraction.selectionState = strongSelf.state.selectedMessageIds.flatMap { ChatInterfaceSelectionState(selectedIds: $0) } strongSelf.paneContainerNode.updateSelectedMessageIds(strongSelf.state.selectedMessageIds, animated: true) - case .search: - strongSelf.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + case .search, .searchWithTags, .standaloneSearch: strongSelf.activateSearch() case .more: - if let source = source { - strongSelf.displayMediaGalleryContextMenu(source: source, gesture: gesture) + if let currentPaneKey = strongSelf.paneContainerNode.currentPaneKey, case .savedMessagesChats = currentPaneKey { + if let controller = strongSelf.controller, let source { + PeerInfoScreenImpl.openSavedMessagesMoreMenu(context: strongSelf.context, sourceController: controller, isViewingAsTopics: true, sourceView: source.view, gesture: gesture) + } + } else { + if let source = source { + strongSelf.displayMediaGalleryContextMenu(source: source, gesture: gesture) + } } case .qrCode: strongSelf.openQrCode() @@ -4694,7 +4712,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } self.view.endEditing(true) - return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in + return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: self.chatLocation, chatFilterTag: nil, chatLocationContextHolder: self.chatLocationContextHolder, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in self?.view.endEditing(true) }, present: { [weak self] c, a in self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -5686,7 +5704,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false @@ -6582,7 +6600,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false @@ -7463,7 +7481,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false @@ -9319,7 +9337,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro private func deleteMessages(messageIds: Set?) { if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { - self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(engine: self.context.engine, accountPeerId: self.context.account.peerId, messageIds: messageIds) + self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(engine: self.context.engine, accountPeerId: self.context.account.peerId, messageIds: messageIds, keepUpdated: false) |> deliverOnMainQueue).startStrict(next: { [weak self] actions in if let strongSelf = self, let peer = strongSelf.data?.peer, !actions.options.isEmpty { let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) @@ -9481,7 +9499,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false @@ -9592,6 +9610,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } + if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessages = currentPaneKey, let paneNode = self.paneContainerNode.currentPane?.node as? PeerInfoChatPaneNode { + paneNode.activateSearch() + return + } else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessagesChats = currentPaneKey, let paneNode = self.paneContainerNode.currentPane?.node as? PeerInfoChatListPaneNode { + paneNode.activateSearch() + return + } + + self.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + if self.isSettings { (self.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.3, curve: .linear)) @@ -10126,9 +10154,31 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } + private func updateNavigationHeight(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat { + var navigationHeight = defaultHeight + if let customNavigationContentNode = self.headerNode.customNavigationContentNode { + var mappedTransition = transition + if customNavigationContentNode.supernode == nil { + mappedTransition = .immediate + } + let contentHeight = customNavigationContentNode.update(width: width, defaultHeight: defaultHeight, insets: insets, transition: mappedTransition) + navigationHeight = contentHeight + } + return navigationHeight + } + func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition, additive: Bool = false) { self.validLayout = (layout, navigationHeight) + self.headerNode.customNavigationContentNode = self.paneContainerNode.currentPane?.node.navigationContentNode + + let isScrollEnabled = !self.isMediaOnly && self.headerNode.customNavigationContentNode == nil + if self.scrollNode.view.isScrollEnabled != isScrollEnabled { + self.scrollNode.view.isScrollEnabled = isScrollEnabled + } + + let navigationHeight = self.updateNavigationHeight(width: layout.size.width, defaultHeight: navigationHeight, insets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right), transition: transition) + if self.headerNode.isAvatarExpanded && layout.size.width > layout.size.height { self.headerNode.updateIsAvatarExpanded(false, transition: transition) self.updateNavigationExpansionPresentation(isExpanded: false, animated: true) @@ -10462,7 +10512,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } self.ignoreScrolling = false - self.updateNavigation(transition: transition, additive: additive, animateHeader: true) + self.updateNavigation(transition: transition, additive: additive, animateHeader: self.controller?.didAppear ?? false) if !self.didSetReady && self.data != nil { self.didSetReady = true @@ -10511,6 +10561,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } if let (layout, navigationHeight) = self.validLayout { + let navigationHeight = self.updateNavigationHeight(width: layout.size.width, defaultHeight: navigationHeight, insets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right), transition: transition) + if !additive { let sectionInset: CGFloat if layout.size.width >= 375.0 { @@ -10545,7 +10597,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let navigationBarHeight: CGFloat = !self.isSettings && layout.isModalOverlay ? 56.0 : 44.0 - self.paneContainerNode.update(size: self.paneContainerNode.bounds.size, sideInset: layout.safeInsets.left, bottomInset: bottomInset, deviceMetrics: layout.deviceMetrics, visibleHeight: visibleHeight, expansionFraction: effectiveAreaExpansionFraction, presentationData: self.presentationData, data: self.data, transition: transition) + self.paneContainerNode.update(size: self.paneContainerNode.bounds.size, sideInset: layout.safeInsets.left, bottomInset: bottomInset, deviceMetrics: layout.deviceMetrics, visibleHeight: visibleHeight, expansionFraction: effectiveAreaExpansionFraction, presentationData: self.presentationData, data: self.data, areTabsHidden: self.headerNode.customNavigationContentNode != nil, navigationHeight: navigationHeight, transition: transition) transition.updateFrame(node: self.headerNode.navigationButtonContainer, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.statusBarHeight ?? 0.0), size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight))) @@ -10570,8 +10622,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.state.selectedMessageIds == nil { if let currentPaneKey = self.paneContainerNode.currentPaneKey { switch currentPaneKey { - case .files, .music, .links, .members, .savedMessagesChats: + case .files, .music, .links, .members: rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) + case .savedMessagesChats: + if let data = self.data, data.hasSavedMessageTags { + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .searchWithTags, isForExpandedView: true)) + } else { + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .standaloneSearch, isForExpandedView: true)) + } + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true)) + case .savedMessages: + if let data = self.data, data.hasSavedMessageTags { + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .searchWithTags, isForExpandedView: true)) + } else { + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) + } case .media: rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true)) default: @@ -10595,7 +10660,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .back, isForExpandedView: false)) } } - self.headerNode.navigationButtonContainer.update(size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight), presentationData: self.presentationData, leftButtons: leftNavigationButtons, rightButtons: rightNavigationButtons, expandFraction: effectiveAreaExpansionFraction, transition: transition) + self.headerNode.navigationButtonContainer.update(size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight), presentationData: self.presentationData, leftButtons: leftNavigationButtons, rightButtons: rightNavigationButtons, expandFraction: effectiveAreaExpansionFraction, shouldAnimateIn: animateHeader, transition: transition) } } @@ -10899,6 +10964,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } } + var didAppear: Bool = false + private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false) { @@ -11422,6 +11489,10 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + DispatchQueue.main.async { [weak self] in + self?.didAppear = true + } + var chatNavigationStack: [ChatNavigationStackItem] = [] if !self.isSettings, let summary = self.customNavigationDataSummary as? ChatControllerNavigationDataSummary { chatNavigationStack.removeAll() @@ -11543,6 +11614,78 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc ] } } + + public static func openSavedMessagesMoreMenu(context: AccountContext, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> deliverOnMainQueue).startStandalone(next: { peer in + guard let peer else { + return + } + + let strings = context.sharedContext.currentPresentationData.with { $0 }.strings + + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: strings.Chat_SavedMessagesModeMenu_ViewAsChats, icon: { theme in + if !isViewingAsTopics { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .clear) + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { [weak sourceController] _, a in + a(.default) + + guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { + return + } + + context.engine.peers.updateSavedMessagesViewAsTopics(value: true) + + if let infoController = navigationController.viewControllers.first(where: { c in + if let c = c as? PeerInfoScreenImpl, case .peer(context.account.peerId) = c.chatLocation { + return true + } + return false + }) { + let _ = navigationController.popToViewController(infoController, animated: false) + } else { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + navigationController.replaceController(sourceController, with: infoController, animated: false) + } + } + }))) + items.append(.action(ContextMenuActionItem(text: strings.Chat_SavedMessagesModeMenu_ViewAsMessages, icon: { theme in + if isViewingAsTopics { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .clear) + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { [weak sourceController] _, a in + a(.default) + + guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { + return + } + + if let chatController = navigationController.viewControllers.first(where: { c in + if let c = c as? ChatController, case .peer(context.account.peerId) = c.chatLocation { + return true + } + return false + }) { + let _ = navigationController.popToViewController(chatController, animated: false) + } else { + let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: context.account.peerId), subject: nil, botStart: nil, mode: .standard(.default)) + + navigationController.replaceController(sourceController, with: chatController, animated: false) + } + + context.engine.peers.updateSavedMessagesViewAsTopics(value: false) + }))) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: sourceController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + sourceController.presentInGlobalOverlay(contextController) + }) + } } private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource { @@ -12709,3 +12852,17 @@ private final class PeerInfoControllerContextReferenceContentSource: ContextRefe return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) } } + +private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceView: UIView + + init(controller: ViewController, sourceView: UIView) { + self.controller = controller + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift index cb020bae7e4..505d0b4b3b4 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift @@ -486,6 +486,7 @@ final class PeerInfoStoryGridScreenComponent: Component { visibleHeight: availableSize.height, isScrollingLockedAtTop: false, expandProgress: 1.0, + navigationHeight: 0.0, presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }), synchronous: false, transition: transition.containedViewLayoutTransition diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 7491954a5df..b7d525ded01 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -950,7 +950,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr public private(set) var isSelectionModeActive: Bool - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -1730,12 +1730,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) { self.items = items - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { + if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { var gridSnapshot: UIView? if reloadAtTop { gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false) } - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: false, transition: .immediate) + self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: .immediate) self.updateSelectedItems(animated: false) if let gridSnapshot = gridSnapshot { self.view.addSubview(gridSnapshot) @@ -2006,8 +2006,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift index b021d2470e0..5de4a34aaac 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift @@ -1102,7 +1102,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, return self._itemInteraction! } - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -1219,7 +1219,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, if threadId == nil { switch contentType { case .photoOrVideo, .photo, .video: - self.calendarSource = self.context.engine.messages.sparseMessageCalendar(peerId: self.peerId, threadId: threadId, tag: tagMaskForType(self.contentType)) + self.calendarSource = self.context.engine.messages.sparseMessageCalendar(peerId: self.peerId, threadId: threadId, tag: tagMaskForType(self.contentType), displayMedia: true) default: self.calendarSource = nil } @@ -1606,7 +1606,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, self.presentationDataDisposable = (self.context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in - guard let strongSelf = self, let (size, topInset, sideInset, bottomInset, _, _, _, _, _) = strongSelf.currentParams else { + guard let strongSelf = self, let (size, topInset, sideInset, bottomInset, _, _, _, _, _, _) = strongSelf.currentParams else { return } strongSelf.itemGridBinding.updatePresentationData(presentationData: presentationData) @@ -1743,12 +1743,12 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) { self.items = items - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { + if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { var gridSnapshot: UIView? if reloadAtTop { gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false) } - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: false, transition: .immediate) + self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: .immediate) if let gridSnapshot = gridSnapshot { self.view.addSubview(gridSnapshot) gridSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak gridSnapshot] _ in @@ -2037,7 +2037,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, switch self.contentType { case .files, .music, .voiceAndVideoMessages: self.itemGrid.forEachVisibleItem { item in - guard let itemView = item.view as? ItemView, let (size, topInset, sideInset, bottomInset, _, _, _, _, _) = self.currentParams else { + guard let itemView = item.view as? ItemView, let (size, topInset, sideInset, bottomInset, _, _, _, _, _, _) = self.currentParams else { return } if let item = itemView.item { @@ -2094,8 +2094,8 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, } } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 404eeb7ac20..976f3db059b 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -339,6 +339,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, setupReplyMessage: { _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in + }, cancelMessageSelection: { _ in }, deleteSelectedMessages: { }, reportSelectedMessages: { }, reportMessages: { _, _ in @@ -731,6 +732,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, openPremiumGift: { }, openPremiumRequiredForMessaging: { }, updateHistoryFilter: { _ in + }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in }, chatController: { return nil diff --git a/submodules/TelegramUI/Components/PlainButtonComponent/Sources/PlainButtonComponent.swift b/submodules/TelegramUI/Components/PlainButtonComponent/Sources/PlainButtonComponent.swift index 672468188a7..cfce4784d2a 100644 --- a/submodules/TelegramUI/Components/PlainButtonComponent/Sources/PlainButtonComponent.swift +++ b/submodules/TelegramUI/Components/PlainButtonComponent/Sources/PlainButtonComponent.swift @@ -17,6 +17,8 @@ public final class PlainButtonComponent: Component { public let action: () -> Void public let isEnabled: Bool public let animateAlpha: Bool + public let animateScale: Bool + public let animateContents: Bool public let tag: AnyObject? public init( @@ -27,7 +29,9 @@ public final class PlainButtonComponent: Component { action: @escaping () -> Void, isEnabled: Bool = true, animateAlpha: Bool = true, - tag : AnyObject? = nil + animateScale: Bool = true, + animateContents: Bool = true, + tag: AnyObject? = nil ) { self.content = content self.effectAlignment = effectAlignment @@ -36,6 +40,8 @@ public final class PlainButtonComponent: Component { self.action = action self.isEnabled = isEnabled self.animateAlpha = animateAlpha + self.animateScale = animateScale + self.animateContents = animateContents self.tag = tag } @@ -58,6 +64,12 @@ public final class PlainButtonComponent: Component { if lhs.animateAlpha != rhs.animateAlpha { return false } + if lhs.animateScale != rhs.animateScale { + return false + } + if lhs.animateContents != rhs.animateContents { + return false + } if lhs.tag !== rhs.tag { return false } @@ -98,35 +110,40 @@ public final class PlainButtonComponent: Component { self.highligthedChanged = { [weak self] highlighted in if let self, self.bounds.width > 0.0 { let animateAlpha = self.component?.animateAlpha ?? true + let animateScale = self.component?.animateScale ?? true let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width if highlighted { self.contentContainer.layer.removeAnimation(forKey: "opacity") - self.contentContainer.layer.removeAnimation(forKey: "sublayerTransform") + self.contentContainer.layer.removeAnimation(forKey: "transform.scale") if animateAlpha { self.contentContainer.alpha = 0.7 } - let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) - transition.setScale(layer: self.contentContainer.layer, scale: topScale) + if animateScale { + let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) + transition.setScale(layer: self.contentContainer.layer, scale: topScale) + } } else { if animateAlpha { self.contentContainer.alpha = 1.0 self.contentContainer.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2) } - let transition = Transition(animation: .none) - transition.setScale(layer: self.contentContainer.layer, scale: 1.0) - - self.contentContainer.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in - guard let self else { - return - } + if animateScale { + let transition = Transition(animation: .none) + transition.setScale(layer: self.contentContainer.layer, scale: 1.0) - self.contentContainer.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue) - }) + self.contentContainer.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in + guard let self else { + return + } + + self.contentContainer.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue) + }) + } } } } @@ -169,7 +186,7 @@ public final class PlainButtonComponent: Component { let contentAlpha: CGFloat = 1.0 let contentSize = self.content.update( - transition: transition, + transition: component.animateContents ? transition : transition.withAnimation(.none), component: component.content, environment: {}, containerSize: availableSize @@ -186,13 +203,30 @@ public final class PlainButtonComponent: Component { if let contentView = self.content.view { var contentTransition = transition if contentView.superview == nil { + let anchorX: CGFloat + switch component.effectAlignment { + case .left: + anchorX = 0.0 + case .center: + anchorX = 0.5 + case .right: + anchorX = 1.0 + } + contentView.layer.anchorPoint = CGPoint(x: anchorX, y: 0.5) + contentTransition = .immediate contentView.isUserInteractionEnabled = false self.contentContainer.addSubview(contentView) } let contentFrame = CGRect(origin: CGPoint(x: component.contentInsets.left + floor((size.width - component.contentInsets.left - component.contentInsets.right - contentSize.width) * 0.5), y: component.contentInsets.top + floor((size.height - component.contentInsets.top - component.contentInsets.bottom - contentSize.height) * 0.5)), size: contentSize) - contentTransition.setFrame(view: contentView, frame: contentFrame) + contentTransition.setPosition(view: contentView, position: CGPoint(x: contentFrame.minX + contentFrame.width * contentView.layer.anchorPoint.x, y: contentFrame.minY + contentFrame.height * contentView.layer.anchorPoint.y)) + + if component.animateContents { + contentTransition.setBounds(view: contentView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size)) + } else { + contentView.bounds = CGRect(origin: CGPoint(), size: contentFrame.size) + } contentTransition.setAlpha(view: contentView, alpha: contentAlpha) } diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 78a4cc16054..89dee442cb1 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -937,7 +937,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, diff --git a/submodules/TelegramUI/Components/SliderContextItem/Sources/SliderContextItem.swift b/submodules/TelegramUI/Components/SliderContextItem/Sources/SliderContextItem.swift index 16b3e0742e1..5272830366e 100644 --- a/submodules/TelegramUI/Components/SliderContextItem/Sources/SliderContextItem.swift +++ b/submodules/TelegramUI/Components/SliderContextItem/Sources/SliderContextItem.swift @@ -27,7 +27,7 @@ public final class SliderContextItem: ContextMenuCustomItem { private let textFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers) -private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode { +private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode, UIGestureRecognizerDelegate { private var presentationData: PresentationData private(set) var vibrancyEffectView: UIVisualEffectView? @@ -122,6 +122,8 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode override func didLoad() { super.didLoad() + self.view.disablesInteractiveTransitionGestureRecognizer = true + if let vibrancyEffectView = self.vibrancyEffectView { Queue.mainQueue().after(0.05) { if let effectNode = findEffectNode(node: self.supernode) { @@ -132,6 +134,7 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode } let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + panGestureRecognizer.delegate = self self.view.addGestureRecognizer(panGestureRecognizer) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) @@ -201,7 +204,7 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode self.updateValue(transition: transition) }) } - + @objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) { let range = self.maxValue - self.minValue switch gestureRecognizer.state { diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index 3e2661e6f35..8f1f89d7146 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -2509,6 +2509,7 @@ final class StorageUsageScreenComponent: Component { let _ = (chatMediaListPreviewControllerData( context: component.context, chatLocation: .peer(id: message.id.peerId), + chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: true, @@ -2725,6 +2726,7 @@ final class StorageUsageScreenComponent: Component { let _ = component.context.sharedContext.openChatMessage(OpenChatMessageParams( context: component.context, chatLocation: .peer(id: message.id.peerId), + chatFilterTag: nil, chatLocationContextHolder: nil, message: galleryMessage, standalone: true, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 6ac68ffe0f7..6bb5c6bbe3c 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -94,7 +94,9 @@ swift_library( "//submodules/Components/BalancedTextComponent", "//submodules/AnimatedCountLabelNode", "//submodules/StickerResources", - "//submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent" + "//submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent", + "//submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen", + "//submodules/TelegramUI/Components/SliderContextItem", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index e1964e5fd18..4fe22421285 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -52,6 +52,22 @@ public final class StoryContentContextImpl: StoryContentContext { context.engine.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: [peerId]) + let preferHighQualityStories: Signal = combineLatest( + context.sharedContext.automaticMediaDownloadSettings + |> map { settings in + return settings.highQualityStories + } + |> distinctUntilChanged, + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) + ) + ) + |> map { setting, peer -> Bool in + let isPremium = peer?.isPremium ?? false + return setting && isPremium + } + |> distinctUntilChanged + var inputKeys: [PostboxViewKey] = [ PostboxViewKey.basicPeer(peerId), PostboxViewKey.cachedPeerData(peerId: peerId), @@ -68,10 +84,11 @@ public final class StoryContentContextImpl: StoryContentContext { context.engine.data.subscribe( TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId) - ) + ), + preferHighQualityStories ) - |> mapToSignal { _, views, data -> Signal<(CombinedView, [PeerId: Peer], (EngineGlobalNotificationSettings, Bool), [MediaId: TelegramMediaFile], [Int64: EngineStoryItem.ForwardInfo], [StoryId: EngineStoryItem?]), NoError> in - return context.account.postbox.transaction { transaction -> (CombinedView, [PeerId: Peer], (EngineGlobalNotificationSettings, Bool), [MediaId: TelegramMediaFile], [Int64: EngineStoryItem.ForwardInfo], [StoryId: EngineStoryItem?]) in + |> mapToSignal { _, views, data, preferHighQualityStories -> Signal<(CombinedView, [PeerId: Peer], (EngineGlobalNotificationSettings, Bool), [MediaId: TelegramMediaFile], [Int64: EngineStoryItem.ForwardInfo], [StoryId: EngineStoryItem?], Bool), NoError> in + return context.account.postbox.transaction { transaction -> (CombinedView, [PeerId: Peer], (EngineGlobalNotificationSettings, Bool), [MediaId: TelegramMediaFile], [Int64: EngineStoryItem.ForwardInfo], [StoryId: EngineStoryItem?], Bool) in var peers: [PeerId: Peer] = [:] var forwardInfoStories: [StoryId: EngineStoryItem?] = [:] var allEntityFiles: [MediaId: TelegramMediaFile] = [:] @@ -136,10 +153,10 @@ public final class StoryContentContextImpl: StoryContentContext { } } - return (views, peers, data, allEntityFiles, pendingForwardsInfo, forwardInfoStories) + return (views, peers, data, allEntityFiles, pendingForwardsInfo, forwardInfoStories, preferHighQualityStories) } } - |> deliverOnMainQueue).startStrict(next: { [weak self] views, peers, data, allEntityFiles, pendingForwardsInfo, forwardInfoStories in + |> deliverOnMainQueue).startStrict(next: { [weak self] views, peers, data, allEntityFiles, pendingForwardsInfo, forwardInfoStories, preferHighQualityStories in guard let self else { return } @@ -193,7 +210,8 @@ public final class StoryContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: cachedUserData.voiceMessagesAvailable, presence: peerPresence.flatMap { EnginePeer.Presence($0) }, canViewStats: false, - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) } else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData { additionalPeerData = StoryContentContextState.AdditionalPeerData( @@ -201,7 +219,8 @@ public final class StoryContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: true, presence: peerPresence.flatMap { EnginePeer.Presence($0) }, canViewStats: cachedChannelData.flags.contains(.canViewStats), - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) } else { additionalPeerData = StoryContentContextState.AdditionalPeerData( @@ -209,7 +228,8 @@ public final class StoryContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: true, presence: peerPresence.flatMap { EnginePeer.Presence($0) }, canViewStats: false, - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) } } @@ -219,7 +239,8 @@ public final class StoryContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: true, presence: peerPresence.flatMap { EnginePeer.Presence($0) }, canViewStats: false, - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) } let state = stateView.value?.get(Stories.PeerState.self) @@ -953,11 +974,18 @@ public final class StoryContentContextImpl: StoryContentContext { } } } + + var selectedMedia: EngineMedia + if let slice = stateValue.slice, let alternativeMedia = item.alternativeMedia, !slice.additionalPeerData.preferHighQualityStories { + selectedMedia = alternativeMedia + } else { + selectedMedia = item.media + } + resultResources[mediaId] = StoryPreloadInfo( peer: peerReference, storyId: item.id, - media: item.media, - alternativeMedia: item.alternativeMedia, + media: selectedMedia, reactions: reactions, priority: .top(position: nextPriority) ) @@ -1106,6 +1134,22 @@ public final class SingleStoryContentContextImpl: StoryContentContext { context.engine.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: [storyId.peerId]) + let preferHighQualityStories: Signal = combineLatest( + context.sharedContext.automaticMediaDownloadSettings + |> map { settings in + return settings.highQualityStories + } + |> distinctUntilChanged, + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) + ) + ) + |> map { setting, peer -> Bool in + let isPremium = peer?.isPremium ?? false + return setting && isPremium + } + |> distinctUntilChanged + self.storyDisposable = (combineLatest(queue: .mainQueue(), context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.Peer(id: storyId.peerId), @@ -1172,9 +1216,10 @@ public final class SingleStoryContentContextImpl: StoryContentContext { } return (item, peers, allEntityFiles, stories) } - } + }, + preferHighQualityStories ) - |> deliverOnMainQueue).startStrict(next: { [weak self] data, itemAndPeers in + |> deliverOnMainQueue).startStrict(next: { [weak self] data, itemAndPeers, preferHighQualityStories in guard let self else { return } @@ -1193,7 +1238,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: areVoiceMessagesAvailable, presence: presence, canViewStats: canViewStats, - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) for (storyId, story) in forwardInfoStories { @@ -1364,6 +1410,22 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { context.engine.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: [peerId]) + let preferHighQualityStories: Signal = combineLatest( + context.sharedContext.automaticMediaDownloadSettings + |> map { settings in + return settings.highQualityStories + } + |> distinctUntilChanged, + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) + ) + ) + |> map { setting, peer -> Bool in + let isPremium = peer?.isPremium ?? false + return setting && isPremium + } + |> distinctUntilChanged + self.storyDisposable = (combineLatest(queue: .mainQueue(), context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), @@ -1375,9 +1437,10 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId) ), listContext.state, - self.focusedIdUpdated.get() + self.focusedIdUpdated.get(), + preferHighQualityStories ) - |> deliverOnMainQueue).startStrict(next: { [weak self] data, state, _ in + |> deliverOnMainQueue).startStrict(next: { [weak self] data, state, _, preferHighQualityStories in guard let self else { return } @@ -1395,7 +1458,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: areVoiceMessagesAvailable, presence: presence, canViewStats: canViewStats, - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) self.listState = state @@ -1549,11 +1613,17 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { } } + var selectedMedia: EngineMedia + if let alternativeMedia = item.alternativeMedia, !preferHighQualityStories { + selectedMedia = alternativeMedia + } else { + selectedMedia = item.media + } + resultResources[mediaId] = StoryPreloadInfo( peer: peerReference, storyId: item.id, - media: item.media, - alternativeMedia: item.alternativeMedia, + media: selectedMedia, reactions: reactions, priority: .top(position: nextPriority) ) @@ -1656,12 +1726,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) -> Signal { var signals: [Signal] = [] - let selectedMedia: EngineMedia - if context.sharedContext.immediateExperimentalUISettings.alternativeStoryMedia, let alternativeMedia = info.alternativeMedia { - selectedMedia = alternativeMedia - } else { - selectedMedia = info.media - } + let selectedMedia: EngineMedia = info.media switch selectedMedia { case let .image(image): @@ -1813,10 +1878,30 @@ public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) - } public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: EnginePeer.Id, storyItem: EngineStoryItem) -> Signal { - return context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + let preferHighQualityStories: Signal = combineLatest( + context.sharedContext.automaticMediaDownloadSettings + |> map { settings in + return settings.highQualityStories + } + |> distinctUntilChanged, + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) + ) ) - |> mapToSignal { peerValue -> Signal in + |> map { setting, peer -> Bool in + let isPremium = peer?.isPremium ?? false + return setting && isPremium + } + |> distinctUntilChanged + + return combineLatest( + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ), + preferHighQualityStories + |> take(1) + ) + |> mapToSignal { peerValue, preferHighQualityStories -> Signal in guard let peerValue else { return .complete() } @@ -1828,8 +1913,15 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine var loadSignals: [Signal] = [] var fetchPriorityDisposable: Disposable? + let selectedMedia: EngineMedia + if !preferHighQualityStories, let alternativeMedia = storyItem.alternativeMedia { + selectedMedia = alternativeMedia + } else { + selectedMedia = storyItem.media + } + var fetchPriorityResourceId: String? - switch storyItem.media { + switch selectedMedia { case let .image(image): if let representation = largestImageRepresentation(image.representations) { fetchPriorityResourceId = representation.resource.id.stringRepresentation @@ -1844,7 +1936,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine fetchPriorityDisposable = context.engine.resources.pushPriorityDownload(resourceId: fetchPriorityResourceId, priority: 2) } - switch storyItem.media { + switch selectedMedia { case let .image(image): if let representation = largestImageRepresentation(image.representations) { statusSignals.append( @@ -1856,7 +1948,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine |> ignoreValues ) - loadSignals.append(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .story, reference: .media(media: .story(peer: peer, id: storyItem.id, media: storyItem.media._asMedia()), resource: representation.resource), range: nil) + loadSignals.append(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .story, reference: .media(media: .story(peer: peer, id: storyItem.id, media: selectedMedia._asMedia()), resource: representation.resource), range: nil) |> ignoreValues |> `catch` { _ -> Signal in return .complete() @@ -1886,7 +1978,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine |> ignoreValues ) - loadSignals.append(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .story, reference: .media(media: .story(peer: peer, id: storyItem.id, media: storyItem.media._asMedia()), resource: file.resource), range: fetchRange) + loadSignals.append(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .story, reference: .media(media: .story(peer: peer, id: storyItem.id, media: selectedMedia._asMedia()), resource: file.resource), range: fetchRange) |> ignoreValues |> `catch` { _ -> Signal in return .complete() @@ -2144,6 +2236,22 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { context.engine.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: [peerId]) + let preferHighQualityStories: Signal = combineLatest( + context.sharedContext.automaticMediaDownloadSettings + |> map { settings in + return settings.highQualityStories + } + |> distinctUntilChanged, + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) + ) + ) + |> map { setting, peer -> Bool in + let isPremium = peer?.isPremium ?? false + return setting && isPremium + } + |> distinctUntilChanged + let originalStoryId = StoryId(peerId: originalPeerId, id: originalStory.id) let inputKeys: [PostboxViewKey] = [ @@ -2159,10 +2267,11 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { context.engine.data.subscribe( TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId) - ) + ), + preferHighQualityStories ) - |> mapToSignal { _, views, data -> Signal<(CombinedView, [PeerId: Peer], (EngineGlobalNotificationSettings, Bool), [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]), NoError> in - return context.account.postbox.transaction { transaction -> (CombinedView, [PeerId: Peer], (EngineGlobalNotificationSettings, Bool), [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]) in + |> mapToSignal { _, views, data, preferHighQualityStories -> Signal<(CombinedView, [PeerId: Peer], (EngineGlobalNotificationSettings, Bool), [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?], Bool), NoError> in + return context.account.postbox.transaction { transaction -> (CombinedView, [PeerId: Peer], (EngineGlobalNotificationSettings, Bool), [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?], Bool) in var peers: [PeerId: Peer] = [:] var forwardInfoStories: [StoryId: EngineStoryItem?] = [:] var allEntityFiles: [MediaId: TelegramMediaFile] = [:] @@ -2204,10 +2313,10 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { } } - return (views, peers, data, allEntityFiles, forwardInfoStories) + return (views, peers, data, allEntityFiles, forwardInfoStories, preferHighQualityStories) } } - |> deliverOnMainQueue).startStrict(next: { [weak self] views, peers, data, allEntityFiles, forwardInfoStories in + |> deliverOnMainQueue).startStrict(next: { [weak self] views, peers, data, allEntityFiles, forwardInfoStories, preferHighQualityStories in guard let self else { return } @@ -2257,7 +2366,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: cachedUserData.voiceMessagesAvailable, presence: peerPresence.flatMap { EnginePeer.Presence($0) }, canViewStats: false, - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) } else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData { additionalPeerData = StoryContentContextState.AdditionalPeerData( @@ -2265,7 +2375,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: true, presence: peerPresence.flatMap { EnginePeer.Presence($0) }, canViewStats: cachedChannelData.flags.contains(.canViewStats), - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) } else { additionalPeerData = StoryContentContextState.AdditionalPeerData( @@ -2273,7 +2384,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: true, presence: peerPresence.flatMap { EnginePeer.Presence($0) }, canViewStats: false, - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) } } @@ -2283,7 +2395,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { areVoiceMessagesAvailable: true, presence: peerPresence.flatMap { EnginePeer.Presence($0) }, canViewStats: false, - isPremiumRequiredForMessaging: isPremiumRequiredForMessaging + isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, + preferHighQualityStories: preferHighQualityStories ) } @@ -2720,11 +2833,18 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { } } } + + var selectedMedia: EngineMedia + if let slice = stateValue.slice, let alternativeMedia = item.alternativeMedia, !slice.additionalPeerData.preferHighQualityStories { + selectedMedia = alternativeMedia + } else { + selectedMedia = item.media + } + resultResources[mediaId] = StoryPreloadInfo( peer: peerReference, storyId: item.id, - media: item.media, - alternativeMedia: item.alternativeMedia, + media: selectedMedia, reactions: reactions, priority: .top(position: nextPriority) ) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 694e57159d8..e3c27064bcd 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -427,6 +427,8 @@ private final class StoryContainerScreenComponent: Component { private var previousSeekTime: Double? private var initialSeekTimestamp: Double? + private var isUpdating: Bool = false + override init(frame: CGRect) { self.backgroundLayer = SimpleLayer() self.backgroundLayer.backgroundColor = UIColor.black.cgColor @@ -486,7 +488,9 @@ private final class StoryContainerScreenComponent: Component { if point != nil { if !self.isHoldingTouch { self.isHoldingTouch = true - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } } else { DispatchQueue.main.async { [weak self] in @@ -496,7 +500,9 @@ private final class StoryContainerScreenComponent: Component { if self.isHoldingTouch { self.isHoldingTouch = false - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } } } @@ -606,14 +612,18 @@ private final class StoryContainerScreenComponent: Component { } } self.itemSetPinchState = StoryItemSetContainerComponent.PinchState(scale: scale, location: pinchLocation, offset: offset) - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } pinchRecognizer.ended = { [weak self] in guard let self else { return } self.itemSetPinchState = nil - self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) + if !self.isUpdating { + self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) + } } self.addGestureRecognizer(pinchRecognizer) @@ -677,7 +687,9 @@ private final class StoryContainerScreenComponent: Component { } } - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } } }) @@ -764,12 +776,16 @@ private final class StoryContainerScreenComponent: Component { if let itemSetPanState = self.itemSetPanState, !itemSetPanState.didBegin { self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true) if !updateImmediately { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) + if !self.isUpdating { + self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) + } } } else { self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: true) if !updateImmediately { - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } } @@ -801,7 +817,9 @@ private final class StoryContainerScreenComponent: Component { itemSetPanState.fraction = fraction self.itemSetPanState = itemSetPanState - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } } @@ -841,7 +859,9 @@ private final class StoryContainerScreenComponent: Component { itemSetPanState.fraction = itemSetPanState.fraction - 1.0 } self.itemSetPanState = itemSetPanState - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } else { shouldDismiss = mayDismiss } @@ -851,14 +871,18 @@ private final class StoryContainerScreenComponent: Component { self.itemSetPanState = itemSetPanState let transition = Transition(animation: .curve(duration: 0.4, curve: .spring)) - self.state?.updated(transition: transition) + if !self.isUpdating { + self.state?.updated(transition: transition) + } transition.attachAnimation(view: self, id: "panState", completion: { [weak self] completed in guard let self, completed else { return } self.itemSetPanState = nil - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } /*if let component = self.component { component.content.resetSideStates() @@ -890,12 +914,16 @@ private final class StoryContainerScreenComponent: Component { case .began: if self.itemSetPanState == nil { self.itemSetPanState = ItemSetPanState(fraction: 0.0, didBegin: false) - self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) + if !self.isUpdating { + self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) + } } case .cancelled, .ended: if let itemSetPanState = self.itemSetPanState, !itemSetPanState.didBegin { self.itemSetPanState = nil - self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) + if !self.isUpdating { + self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) + } } default: break @@ -977,15 +1005,21 @@ private final class StoryContainerScreenComponent: Component { } self.didAnimateIn = true - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } }) } else { self.didAnimateIn = true - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } } else { self.didAnimateIn = true - self.state?.updated(transition: .immediate) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } } else { self.layer.allowsGroupOpacity = true @@ -1180,6 +1214,11 @@ private final class StoryContainerScreenComponent: Component { return availableSize } + self.isUpdating = true + defer { + self.isUpdating = false + } + let environment = environment[ViewControllerComponentContainer.Environment.self].value self.environment = environment @@ -1308,9 +1347,9 @@ private final class StoryContainerScreenComponent: Component { if self.stateValue?.slice == nil { self.environment?.controller()?.dismiss() } else { - let startTime = CFAbsoluteTimeGetCurrent() - self.state?.updated(transition: .immediate) - print("update time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } } } else { DispatchQueue.main.async { [weak self] in diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index 860d7ace4f6..4afd4c85dc2 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -31,6 +31,7 @@ public final class StoryContentItem: Equatable { public final class SharedState { public var replyDrafts: [StoryId: NSAttributedString] = [:] + public var baseRate: Double = 1.0 public init() { } @@ -55,6 +56,9 @@ public final class StoryContentItem: Equatable { open func enterAmbientMode(ambient: Bool) { } + open func setBaseRate(_ baseRate: Double) { + } + open var videoPlaybackPosition: Double? { return nil } @@ -142,19 +146,22 @@ public final class StoryContentContextState { public let presence: EnginePeer.Presence? public let canViewStats: Bool public let isPremiumRequiredForMessaging: Bool + public let preferHighQualityStories: Bool public init( isMuted: Bool, areVoiceMessagesAvailable: Bool, presence: EnginePeer.Presence?, canViewStats: Bool, - isPremiumRequiredForMessaging: Bool + isPremiumRequiredForMessaging: Bool, + preferHighQualityStories: Bool ) { self.isMuted = isMuted self.areVoiceMessagesAvailable = areVoiceMessagesAvailable self.presence = presence self.canViewStats = canViewStats self.isPremiumRequiredForMessaging = isPremiumRequiredForMessaging + self.preferHighQualityStories = preferHighQualityStories } public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool { @@ -173,6 +180,9 @@ public final class StoryContentContextState { if lhs.isPremiumRequiredForMessaging != rhs.isPremiumRequiredForMessaging { return false } + if lhs.preferHighQualityStories != rhs.preferHighQualityStories { + return false + } return true } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index be2f161580e..76fb1778b63 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -33,11 +33,13 @@ final class StoryItemContentComponent: Component { let availableReactions: StoryAvailableReactions? let entityFiles: [MediaId: TelegramMediaFile] let audioMode: StoryContentItem.AudioMode + let baseRate: Double let isVideoBuffering: Bool let isCurrent: Bool + let preferHighQuality: Bool let activateReaction: (UIView, MessageReaction.Reaction) -> Void - init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, isVideoBuffering: Bool, isCurrent: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) { + init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) { self.context = context self.strings = strings self.peer = peer @@ -45,8 +47,10 @@ final class StoryItemContentComponent: Component { self.entityFiles = entityFiles self.availableReactions = availableReactions self.audioMode = audioMode + self.baseRate = baseRate self.isVideoBuffering = isVideoBuffering self.isCurrent = isCurrent + self.preferHighQuality = preferHighQuality self.activateReaction = activateReaction } @@ -69,11 +73,17 @@ final class StoryItemContentComponent: Component { if lhs.entityFiles.keys != rhs.entityFiles.keys { return false } + if lhs.baseRate != rhs.baseRate { + return false + } if lhs.isVideoBuffering != rhs.isVideoBuffering { return false } if lhs.isCurrent != rhs.isCurrent { return false + } + if lhs.preferHighQuality != rhs.preferHighQuality { + return false } return true } @@ -112,7 +122,7 @@ final class StoryItemContentComponent: Component { override var videoPlaybackPosition: Double? { return self.videoPlaybackStatus?.timestamp } - + private let hierarchyTrackingLayer: HierarchyTrackingLayer private var fetchPriorityResourceId: String? @@ -221,6 +231,7 @@ final class StoryItemContentComponent: Component { priority: .gallery ) videoNode.isHidden = true + videoNode.setBaseRate(component.baseRate) self.videoNode = videoNode self.insertSubview(videoNode.view, aboveSubview: self.imageView) @@ -325,6 +336,12 @@ final class StoryItemContentComponent: Component { } } + override func setBaseRate(_ baseRate: Double) { + if let videoNode = self.videoNode { + videoNode.setBaseRate(baseRate) + } + } + private func updateProgressMode(update: Bool) { if let videoNode = self.videoNode { let canPlay = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy @@ -576,7 +593,7 @@ final class StoryItemContentComponent: Component { let selectedMedia: EngineMedia var messageMedia: EngineMedia? - if component.context.sharedContext.immediateExperimentalUISettings.alternativeStoryMedia, let alternativeMedia = component.item.alternativeMedia { + if !component.preferHighQuality, let alternativeMedia = component.item.alternativeMedia { selectedMedia = alternativeMedia switch alternativeMedia { @@ -610,6 +627,9 @@ final class StoryItemContentComponent: Component { reloadMedia = true if let videoNode = self.videoNode { + self.videoProgressDisposable?.dispose() + self.videoProgressDisposable = nil + self.videoNode = nil videoNode.view.removeFromSuperview() } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 6d83d22d84e..1832753fc5b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -42,6 +42,7 @@ import TranslateUI import TelegramUIPreferences import StoryFooterPanelComponent import TelegramNotices +import SliderContextItem public final class StoryAvailableReactions: Equatable { let reactionItems: [ReactionItem] @@ -1565,8 +1566,10 @@ public final class StoryItemSetContainerComponent: Component { availableReactions: component.availableReactions, entityFiles: item.entityFiles, audioMode: component.audioMode, + baseRate: component.storyItemSharedState.baseRate, isVideoBuffering: visibleItem.isBuffering, isCurrent: index == centralIndex, + preferHighQuality: component.slice.additionalPeerData.preferHighQualityStories, activateReaction: { [weak self] reactionView, reaction in guard let self else { return @@ -4379,6 +4382,7 @@ public final class StoryItemSetContainerComponent: Component { items: reactionItems.map(ReactionContextItem.reaction), selectedItems: component.slice.item.storyItem.myReaction.flatMap { Set([$0]) } ?? Set(), title: self.displayLikeReactions ? nil : component.strings.Story_SendReactionAsMessage, + reactionsLocked: false, alwaysAllowPremiumReactions: false, allPresetReactionsAreAvailable: false, getEmojiContent: { [weak self] animationCache, animationRenderer in @@ -5611,6 +5615,15 @@ public final class StoryItemSetContainerComponent: Component { ), nil) } + private func presentQualityUpgradeScreen() { + self.sendMessageContext.presentQualityUpgrade(view: self, action: { [weak self] in + guard let self else { + return + } + self.presentStoriesUpgradeScreen(source: .storiesHigherQuality) + }) + } + private func presentStealthModeUpgradeScreen() { self.sendMessageContext.presentStealthModeUpgrade(view: self, action: { [weak self] in guard let self else { @@ -6021,195 +6034,154 @@ public final class StoryItemSetContainerComponent: Component { return (tip, tipSignal) } - private func performMyMoreAction(sourceView: UIView, gesture: ContextGesture?) { - guard let component = self.component, let controller = component.controller() else { - return + private func contextMenuSpeedItems(value: ValuePromise) -> Signal<[ContextMenuItem], NoError> { + guard let component = self.component else { + return .single([]) } - self.dismissAllTooltips() - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + + let baseRate = component.storyItemSharedState.baseRate + let valuePromise = ValuePromise(nil) + var items: [ContextMenuItem] = [] - - let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0 - let privacyText: String - switch component.slice.item.storyItem.privacy?.base { - case .closeFriends: - privacyText = component.strings.Story_ContextPrivacy_LabelCloseFriends - case .contacts: - if additionalCount != 0 { - privacyText = component.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalCount)").string - } else { - privacyText = component.strings.Story_ContextPrivacy_LabelContacts - } - case .nobody: - if additionalCount != 0 { - privacyText = component.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalCount)) - } else { - privacyText = component.strings.Story_ContextPrivacy_LabelOnlyMe - } - default: - privacyText = component.strings.Story_ContextPrivacy_LabelEveryone - } - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Privacy, textLayout: .secondLineWithValue(privacyText), icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return - } - self.openItemPrivacySettings() + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) + }, iconPosition: .left, action: { c, _ in + c.popItems() }))) - - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { + + items.append(.custom(SliderContextItem(minValue: 0.2, maxValue: 2.5, value: baseRate, valueChanged: { [weak self] newValue, done in + guard let self, let component = self.component else { return } - self.openStoryEditing() - }))) - - 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 - a(.default) + func normalizeValue(_ value: CGFloat) -> CGFloat { + return round(value * 10.0) / 10.0 + } - guard let self, let component = self.component else { - return + let rate = normalizeValue(newValue) + if let visibleItem = self.visibleItems[component.slice.item.storyItem.id], let view = visibleItem.view.view as? StoryItemContentComponent.View { + view.setBaseRate(rate) } - let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).startStandalone() + component.storyItemSharedState.baseRate = rate + valuePromise.set(rate) - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - if component.slice.item.storyItem.isPinned { - self.component?.presentController(UndoOverlayController( - presentationData: presentationData, - content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil, customUndoText: nil), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ), nil) - } else { - self.component?.presentController(UndoOverlayController( - presentationData: presentationData, - content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil, customUndoText: nil), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ), nil) + if done { + value.set(rate) } - }))) + }), true)) - let saveText: String = component.strings.Story_Context_SaveToGallery - items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return - } - self.requestSave() - }))) + items.append(.separator) - if case let .user(accountUser) = component.slice.peer { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextStealthMode, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return - } - if accountUser.isPremium { - self.sendMessageContext.requestStealthMode(view: self) + for (text, _, rate) in speedList(strings: presentationData.strings) { + let isSelected = abs(baseRate - rate) < 0.01 + items.append(.action(ContextMenuActionItem(text: text, icon: { _ in return nil }, iconSource: ContextMenuActionItemIconSource(size: CGSize(width: 24.0, height: 24.0), signal: valuePromise.get() + |> map { value in + if isSelected && value == nil { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white) } else { - self.presentStealthModeUpgradeScreen() + return nil } - }))) - } - - if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) + }), action: { [weak self] _, f in + f(.default) guard let self, let component = self.component else { return } - let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) - |> deliverOnMainQueue).startStandalone(next: { [weak self] link in - guard let self, let component = self.component else { - return - } - if let link { - UIPasteboard.general.string = link - - component.presentController(UndoOverlayController( - presentationData: presentationData, - content: .linkCopied(text: component.strings.Story_ToastLinkCopied), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ), nil) - } - }) - }))) - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return + if let visibleItem = self.visibleItems[component.slice.item.storyItem.id], let view = visibleItem.view.view as? StoryItemContentComponent.View { + view.setBaseRate(rate) } - self.sendMessageContext.performShareAction(view: self) + component.storyItemSharedState.baseRate = rate }))) } - - let (tip, tipSignal) = self.getLinkedStickerPacks() - - let contextItems = ContextController.Items(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 - guard let self else { - return - } - self.contextController = nil - self.updateIsProgressPaused() - } - self.contextController = contextController - self.updateIsProgressPaused() - controller.present(contextController, in: .window(.root)) + + return .single(items) } - private func performMyChannelMoreAction(sourceView: UIView, gesture: ContextGesture?) { + private func performMyMoreAction(sourceView: UIView, gesture: ContextGesture?) { guard let component = self.component, let controller = component.controller() else { return } - guard case let .channel(channel) = component.slice.peer else { - return - } self.dismissAllTooltips() + let baseRatePromise = ValuePromise(component.storyItemSharedState.baseRate) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - var items: [ContextMenuItem] = [] - if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.editStories) { + + 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" + var didSetSpeedValue = false + for (text, iconText, speed) in speedList(strings: presentationData.strings) { + if abs(speed - baseRate) < 0.01 { + speedValue = text + speedIconText = iconText + didSetSpeedValue = true + break + } + } + if !didSetSpeedValue && baseRate != 1.0 { + speedValue = String(format: "%.1fx", baseRate) + speedIconText = speedValue + } + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PlaybackSpeed_Title, textLayout: .secondLineWithValue(speedValue), icon: { theme in + return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + guard let self else { + c.dismiss(completion: nil) + return + } + + c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + }))) + items.append(.separator) + } + + let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0 + let privacyText: String + switch component.slice.item.storyItem.privacy?.base { + case .closeFriends: + privacyText = component.strings.Story_ContextPrivacy_LabelCloseFriends + case .contacts: + if additionalCount != 0 { + privacyText = component.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalCount)").string + } else { + privacyText = component.strings.Story_ContextPrivacy_LabelContacts + } + case .nobody: + if additionalCount != 0 { + privacyText = component.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalCount)) + } else { + privacyText = component.strings.Story_ContextPrivacy_LabelOnlyMe + } + default: + privacyText = component.strings.Story_ContextPrivacy_LabelEveryone + } + + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Privacy, textLayout: .secondLineWithValue(privacyText), icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.openItemPrivacySettings() + }))) + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in @@ -6220,14 +6192,10 @@ public final class StoryItemSetContainerComponent: Component { } self.openStoryEditing() }))) - } - - if !items.isEmpty { + items.append(.separator) - } - - if channel.hasPermission(.editStories) { - items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromChannel : component.strings.Story_Context_SaveToChannel, icon: { theme in + + 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 a(.default) @@ -6240,18 +6208,18 @@ public final class StoryItemSetContainerComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) if component.slice.item.storyItem.isPinned { - self.scheduledStoryUnpinnedUndoOverlay = UndoOverlayController( + self.component?.presentController(UndoOverlayController( presentationData: presentationData, - content: .info(title: nil, text: presentationData.strings.Story_ToastRemovedFromChannelText, timeout: nil, customUndoText: nil), + content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in return false } - ) + ), nil) } else { self.component?.presentController(UndoOverlayController( presentationData: presentationData, - content: .info(title: presentationData.strings.Story_ToastSavedToChannelTitle, text: presentationData.strings.Story_ToastSavedToChannelText, timeout: nil, customUndoText: nil), + content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, blurred: true, @@ -6259,131 +6227,278 @@ public final class StoryItemSetContainerComponent: Component { ), nil) } }))) - } - - if component.slice.additionalPeerData.canViewStats { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_ViewStats, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor) + + let saveText: String = component.strings.Story_Context_SaveToGallery + items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in a(.default) - guard let self, let component = self.component else { + guard let self else { return } - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) - let statsController = component.context.sharedContext.makeStoryStatsController( - context: component.context, - updatedPresentationData: (presentationData, .single(presentationData)), - peerId: component.slice.peer.id, - storyId: component.slice.item.storyItem.id, - storyItem: component.slice.item.storyItem, - fromStory: true - ) - component.controller()?.push(statsController) + self.requestSave() }))) + + if case let .user(accountUser) = component.slice.peer { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextStealthMode, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + if accountUser.isPremium { + self.sendMessageContext.requestStealthMode(view: self) + } else { + self.presentStealthModeUpgradeScreen() + } + }))) + } + + if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + + let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) + |> deliverOnMainQueue).startStandalone(next: { [weak self] link in + guard let self, let component = self.component else { + return + } + if let link { + UIPasteboard.general.string = link + + component.presentController(UndoOverlayController( + presentationData: presentationData, + content: .linkCopied(text: component.strings.Story_ToastLinkCopied), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ), nil) + } + }) + }))) + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.sendMessageContext.performShareAction(view: self) + }))) + } + + let (tip, tipSignal) = self.getLinkedStickerPacks() + + return .single(ContextController.Items(id: 0, content: .list(items), tip: tip, tipSignal: tipSignal)) } - let saveText: String = component.strings.Story_Context_SaveToGallery - items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - + let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: contextItems, gesture: gesture) + contextController.dismissed = { [weak self] in guard let self else { return } - self.requestSave() - }))) + self.contextController = nil + self.updateIsProgressPaused() + } + self.contextController = contextController + self.updateIsProgressPaused() + controller.present(contextController, in: .window(.root)) + } + + private func performMyChannelMoreAction(sourceView: UIView, gesture: ContextGesture?) { + guard let component = self.component, let controller = component.controller() else { + return + } + guard case let .channel(channel) = component.slice.peer else { + return + } - if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self, let component = self.component else { - return + self.dismissAllTooltips() + + 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" + var didSetSpeedValue = false + for (text, iconText, speed) in speedList(strings: presentationData.strings) { + if abs(speed - baseRate) < 0.01 { + speedValue = text + speedIconText = iconText + didSetSpeedValue = true + break + } + } + if !didSetSpeedValue && baseRate != 1.0 { + speedValue = String(format: "%.1fx", baseRate) + speedIconText = speedValue } - let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) - |> deliverOnMainQueue).startStandalone(next: { [weak self] link in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PlaybackSpeed_Title, textLayout: .secondLineWithValue(speedValue), icon: { theme in + return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + guard let self else { + c.dismiss(completion: nil) + return + } + + c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + }))) + items.append(.separator) + } + + if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.editStories) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.openStoryEditing() + }))) + } + + if !items.isEmpty { + items.append(.separator) + } + + if channel.hasPermission(.editStories) { + items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromChannel : component.strings.Story_Context_SaveToChannel, 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 + a(.default) + guard let self, let component = self.component else { return } - if let link { - UIPasteboard.general.string = link - - component.presentController(UndoOverlayController( + + let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).startStandalone() + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + if component.slice.item.storyItem.isPinned { + self.scheduledStoryUnpinnedUndoOverlay = UndoOverlayController( + presentationData: presentationData, + content: .info(title: nil, text: presentationData.strings.Story_ToastRemovedFromChannelText, timeout: nil, customUndoText: nil), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ) + } else { + self.component?.presentController(UndoOverlayController( presentationData: presentationData, - content: .linkCopied(text: component.strings.Story_ToastLinkCopied), + content: .info(title: presentationData.strings.Story_ToastSavedToChannelTitle, text: presentationData.strings.Story_ToastSavedToChannelText, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in return false } ), nil) } - }) - }))) - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in + }))) + } + + if component.slice.additionalPeerData.canViewStats { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_ViewStats, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) + let statsController = component.context.sharedContext.makeStoryStatsController( + context: component.context, + updatedPresentationData: (presentationData, .single(presentationData)), + peerId: component.slice.peer.id, + storyId: component.slice.item.storyItem.id, + storyItem: component.slice.item.storyItem, + fromStory: true + ) + component.controller()?.push(statsController) + }))) + } + + let saveText: String = component.strings.Story_Context_SaveToGallery + items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in a(.default) guard let self else { return } - self.sendMessageContext.performShareAction(view: self) + self.requestSave() }))) - } - - var isHidden = false - if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden { - isHidden = storiesHidden - } - items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - guard let self, let component = self.component else { - return + if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + + let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) + |> deliverOnMainQueue).startStandalone(next: { [weak self] link in + guard let self, let component = self.component else { + return + } + if let link { + UIPasteboard.general.string = link + + component.presentController(UndoOverlayController( + presentationData: presentationData, + content: .linkCopied(text: component.strings.Story_ToastLinkCopied), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ), nil) + } + }) + }))) + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.sendMessageContext.performShareAction(view: self) + }))) } - let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden) - - let text = !isHidden ? component.strings.StoryFeed_TooltipArchive(component.slice.peer.compactDisplayTitle).string : component.strings.StoryFeed_TooltipUnarchive(component.slice.peer.compactDisplayTitle).string - let tooltipScreen = TooltipScreen( - context: component.context, - account: component.context.account, - sharedContext: component.context.sharedContext, - text: .markdown(text: text), - style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0), - icon: .peer(peer: component.slice.peer, isStory: true), - action: TooltipScreen.Action( - title: component.strings.Undo_Undo, - action: { - component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden) - } - ), - location: .bottom, - shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) } - ) - tooltipScreen.willBecomeDismissed = { [weak self] _ in - guard let self else { - return - } - self.sendMessageContext.tooltipScreen = nil - self.updateIsProgressPaused() + var isHidden = false + if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden { + isHidden = storiesHidden } - self.sendMessageContext.tooltipScreen?.dismiss() - self.sendMessageContext.tooltipScreen = tooltipScreen - self.updateIsProgressPaused() - component.controller()?.present(tooltipScreen, in: .current) - }))) - - if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in a(.default) @@ -6391,46 +6506,88 @@ public final class StoryItemSetContainerComponent: Component { return } - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - let actionSheet = ActionSheetController(presentationData: presentationData) + let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden) - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - - guard let self, let component = self.component else { - return - } - component.delete() - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - - actionSheet.dismissed = { [weak self] _ in + let text = !isHidden ? component.strings.StoryFeed_TooltipArchive(component.slice.peer.compactDisplayTitle).string : component.strings.StoryFeed_TooltipUnarchive(component.slice.peer.compactDisplayTitle).string + let tooltipScreen = TooltipScreen( + context: component.context, + account: component.context.account, + sharedContext: component.context.sharedContext, + text: .markdown(text: text), + style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0), + icon: .peer(peer: component.slice.peer, isStory: true), + action: TooltipScreen.Action( + title: component.strings.Undo_Undo, + action: { + component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden) + } + ), + location: .bottom, + shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) } + ) + tooltipScreen.willBecomeDismissed = { [weak self] _ in guard let self else { return } - self.sendMessageContext.actionSheet = nil + self.sendMessageContext.tooltipScreen = nil self.updateIsProgressPaused() } - self.sendMessageContext.actionSheet = actionSheet + self.sendMessageContext.tooltipScreen?.dismiss() + self.sendMessageContext.tooltipScreen = tooltipScreen self.updateIsProgressPaused() - - component.presentController(actionSheet, nil) + component.controller()?.present(tooltipScreen, in: .current) }))) + + if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + let actionSheet = ActionSheetController(presentationData: presentationData) + + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + guard let self, let component = self.component else { + return + } + component.delete() + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + + actionSheet.dismissed = { [weak self] _ in + guard let self else { + return + } + self.sendMessageContext.actionSheet = nil + self.updateIsProgressPaused() + } + self.sendMessageContext.actionSheet = actionSheet + self.updateIsProgressPaused() + + component.presentController(actionSheet, nil) + }))) + } + + let (tip, tipSignal) = self.getLinkedStickerPacks() + return .single(ContextController.Items(id: 0, content: .list(items), tip: tip, tipSignal: tipSignal)) } - let (tip, tipSignal) = self.getLinkedStickerPacks() - - let contextItems = ContextController.Items(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) + let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: contextItems, gesture: gesture) contextController.dismissed = { [weak self] in guard let self else { return @@ -6448,6 +6605,8 @@ public final class StoryItemSetContainerComponent: Component { return } + let baseRatePromise = ValuePromise(component.storyItemSharedState.baseRate) + let translationSettings = component.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) |> map { sharedData -> TranslationSettings in let translationSettings: TranslationSettings @@ -6468,9 +6627,10 @@ public final class StoryItemSetContainerComponent: Component { TelegramEngine.EngineData.Item.Peer.IsContact(id: component.slice.peer.id), TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId) ), - translationSettings + translationSettings, + baseRatePromise.get() ) - |> take(1)).startStandalone(next: { [weak self] result, translationSettings in + |> take(1)).startStandalone(next: { [weak self] result, translationSettings, baseRate in guard let self, let component = self.component, let controller = component.controller() else { return } @@ -6486,6 +6646,36 @@ public final class StoryItemSetContainerComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) var items: [ContextMenuItem] = [] + if case .file = component.slice.item.storyItem.media { + var speedValue: String = presentationData.strings.PlaybackSpeed_Normal + var speedIconText: String = "1x" + var didSetSpeedValue = false + for (text, iconText, speed) in speedList(strings: presentationData.strings) { + if abs(speed - baseRate) < 0.01 { + speedValue = text + speedIconText = iconText + didSetSpeedValue = true + break + } + } + if !didSetSpeedValue && baseRate != 1.0 { + speedValue = String(format: "%.1fx", baseRate) + speedIconText = speedValue + } + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PlaybackSpeed_Title, textLayout: .secondLineWithValue(speedValue), icon: { theme in + return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + guard let self else { + c.dismiss(completion: nil) + return + } + + c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + }))) + items.append(.separator) + } + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers) if !component.slice.peer.isService && isContact { @@ -6536,6 +6726,85 @@ public final class StoryItemSetContainerComponent: Component { }))) } + if !component.slice.peer.isService && component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + + let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) + |> deliverOnMainQueue).startStandalone(next: { [weak self] link in + guard let self, let component = self.component else { + return + } + if let link { + UIPasteboard.general.string = link + + component.presentController(UndoOverlayController( + presentationData: presentationData, + content: .linkCopied(text: component.strings.Story_ToastLinkCopied), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ), nil) + } + }) + }))) + } + + if case let .file(file) = component.slice.item.storyItem.media, file.isVideo { + let isHq = component.slice.additionalPeerData.preferHighQualityStories + items.append(.action(ContextMenuActionItem(text: isHq ? component.strings.Story_ContextMenuSD : component.strings.Story_ContextMenuHD, icon: { theme in + if isHq { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QualitySd"), color: theme.contextMenu.primaryColor) + } else { + return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/QualityHd" : "Chat/Context Menu/QualityHdLocked"), color: theme.contextMenu.primaryColor) + } + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component, let controller = component.controller() else { + return + } + + if !component.slice.additionalPeerData.preferHighQualityStories && !accountUser.isPremium { + self.presentQualityUpgradeScreen() + + return + } + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + let title: String + let text: String + if component.slice.additionalPeerData.preferHighQualityStories { + title = component.strings.Story_ToastQualitySD_Title + text = component.strings.Story_ToastQualitySD_Text + } else { + title = component.strings.Story_ToastQualityHD_Title + text = component.strings.Story_ToastQualityHD_Text + } + controller.present(UndoOverlayController( + presentationData: presentationData, + content: .info(title: title, text: text, timeout: nil, customUndoText: nil), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ), in: .current) + + let _ = updateMediaDownloadSettingsInteractively(accountManager: component.context.sharedContext.accountManager, { settings in + var settings = settings + settings.highQualityStories = !isHq + return settings + }).startStandalone() + }))) + } + var isHidden = false if case let .user(user) = component.slice.peer, let storiesHidden = user.storiesHidden { isHidden = storiesHidden @@ -6633,37 +6902,6 @@ public final class StoryItemSetContainerComponent: Component { }))) } - if !component.slice.peer.isService && component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self, let component = self.component else { - return - } - - let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) - |> deliverOnMainQueue).startStandalone(next: { [weak self] link in - guard let self, let component = self.component else { - return - } - if let link { - UIPasteboard.general.string = link - - component.presentController(UndoOverlayController( - presentationData: presentationData, - content: .linkCopied(text: component.strings.Story_ToastLinkCopied), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ), nil) - } - }) - }))) - } - if component.slice.additionalPeerData.canViewStats { items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_ViewStats, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor) @@ -6939,3 +7177,49 @@ private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to tar return keyframes } + +private func speedList(strings: PresentationStrings) -> [(String, String, Double)] { + return [ + ("0.5x", "0.5x", 0.5), + (strings.PlaybackSpeed_Normal, "1x", 1.0), + ("1.5x", "1.5x", 1.5), + ("2x", "2x", 2.0) + ] +} + +private func optionsRateImage(rate: String, isLarge: Bool, color: UIColor = .white) -> UIImage? { + return generateImage(isLarge ? CGSize(width: 30.0, height: 30.0) : CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in + UIGraphicsPushContext(context) + + context.clear(CGRect(origin: CGPoint(), size: size)) + + if let image = generateTintedImage(image: UIImage(bundleImageName: isLarge ? "Chat/Context Menu/Playspeed30" : "Chat/Context Menu/Playspeed24"), color: .white) { + image.draw(at: CGPoint(x: 0.0, y: 0.0)) + } + + let string = NSMutableAttributedString(string: rate, font: Font.with(size: isLarge ? 11.0 : 10.0, design: .round, weight: .semibold), textColor: color) + + var offset = CGPoint(x: 1.0, y: 0.0) + if rate.count >= 3 { + if rate == "0.5x" { + string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string)) + offset.x += -0.5 + } else { + string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string)) + offset.x += -0.3 + } + } else { + offset.x += -0.3 + } + + if !isLarge { + offset.x *= 0.5 + offset.y *= 0.5 + } + + let boundingRect = string.boundingRect(with: size, options: [], context: nil) + string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + floor((size.height - boundingRect.height) / 2.0))) + + UIGraphicsPopContext() + }) +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 006f7b232ad..5b9643489f9 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -49,6 +49,7 @@ import TelegramNotices import ObjectiveC import LocationUI import ReactionSelectionNode +import StoryQualityUpgradeSheetScreen private var ObjCKey_DeinitWatcher: Int? @@ -1107,7 +1108,7 @@ final class StoryItemSetContainerSendMessage { guard let navigationController = controller.navigationController as? NavigationController else { return } - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false @@ -3277,6 +3278,29 @@ final class StoryItemSetContainerSendMessage { controller.push(sheet) }) } + + func presentQualityUpgrade(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) { + guard let component = view.component, let controller = component.controller() else { + return + } + + let sheet = StoryQualityUpgradeSheetScreen( + context: component.context, + buttonAction: { + action() + } + ) + sheet.wasDismissed = { [weak self, weak view] in + guard let self, let view else { + return + } + self.actionSheet = nil + view.updateIsProgressPaused() + } + self.actionSheet = sheet + view.updateIsProgressPaused() + controller.push(sheet) + } private var selectedMediaArea: MediaArea? func activateMediaArea(view: StoryItemSetContainerComponent.View, mediaArea: MediaArea, position: CGPoint? = nil, immediate: Bool = false) { diff --git a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD new file mode 100644 index 00000000000..316cc15fa87 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StoryQualityUpgradeSheetScreen", + module_name = "StoryQualityUpgradeSheetScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AccountContext", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/SheetComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/LottieComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetContentComponent.swift new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetContentComponent.swift @@ -0,0 +1 @@ + diff --git a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift new file mode 100644 index 00000000000..3db72ef0937 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift @@ -0,0 +1,425 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import ViewControllerComponent +import AccountContext +import SheetComponent +import ButtonComponent +import LottieComponent +import MultilineTextComponent +import BalancedTextComponent +import Markdown +import TelegramStringFormatting +import BundleIconComponent + +public final class ButtonSubtitleComponent: CombinedComponent { + public let title: String + public let color: UIColor + + public init(title: String, color: UIColor) { + self.title = title + self.color = color + } + + public static func ==(lhs: ButtonSubtitleComponent, rhs: ButtonSubtitleComponent) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.color !== rhs.color { + return false + } + return true + } + + public static var body: Body { + let icon = Child(BundleIconComponent.self) + let text = Child(Text.self) + + return { context in + let icon = icon.update( + component: BundleIconComponent( + name: "Chat/Input/Accessory Panels/TextLockIcon", + tintColor: context.component.color, + maxSize: CGSize(width: 10.0, height: 10.0) + ), + availableSize: CGSize(width: 100.0, height: 100.0), + transition: context.transition + ) + + let text = text.update( + component: Text(text: context.component.title, font: Font.medium(11.0), color: context.component.color), + availableSize: CGSize(width: context.availableSize.width - 20.0, height: 100.0), + transition: context.transition + ) + + let spacing: CGFloat = 3.0 + let size = CGSize(width: icon.size.width + spacing + text.size.width, height: text.size.height) + context.add(icon + .position(icon.size.centered(in: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: icon.size.width, height: size.height))).center) + ) + context.add(text + .position(text.size.centered(in: CGRect(origin: CGPoint(x: icon.size.width + spacing, y: 0.0), size: text.size)).center) + ) + + return size + } + } +} + +private final class StoryQualityUpgradeSheetContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let action: () -> Void + let dismiss: () -> Void + + init( + action: @escaping () -> Void, + dismiss: @escaping () -> Void + ) { + self.action = action + self.dismiss = dismiss + } + + static func ==(lhs: StoryQualityUpgradeSheetContentComponent, rhs: StoryQualityUpgradeSheetContentComponent) -> Bool { + return true + } + + final class View: UIView { + private let icon = ComponentView() + private let title = ComponentView() + private let text = ComponentView() + private let button = ComponentView() + + private var cancelButton: ComponentView? + + private var component: StoryQualityUpgradeSheetContentComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func update(component: StoryQualityUpgradeSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[EnvironmentType.self].value + + let sideInset: CGFloat = 16.0 + + let cancelButton: ComponentView + if let current = self.cancelButton { + cancelButton = current + } else { + cancelButton = ComponentView() + self.cancelButton = cancelButton + } + let cancelButtonSize = cancelButton.update( + transition: transition, + component: AnyComponent(Button( + content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.dismiss() + } + ).minSize(CGSize(width: 8.0, height: 44.0))), + environment: {}, + containerSize: CGSize(width: 200.0, height: 100.0) + ) + if let cancelButtonView = cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 6.0), size: cancelButtonSize)) + } + + var contentHeight: CGFloat = 0.0 + contentHeight += 32.0 + + let iconSize = self.icon.update( + transition: transition, + component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "StoryUpgradeSheet"), + color: nil, + startingPosition: .begin, + size: CGSize(width: 100.0, height: 100.0) + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 42.0), size: iconSize)) + } + + contentHeight += 138.0 + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: environment.strings.Story_UpgradeQuality_Title, font: Font.semibold(20.0), textColor: environment.theme.list.itemPrimaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize)) + } + contentHeight += titleSize.height + contentHeight += 14.0 + + let textSize = self.text.update( + transition: transition, + component: AnyComponent(BalancedTextComponent( + text: .plain(NSAttributedString(string: environment.strings.Story_UpgradeQuality_Text, font: Font.regular(14.0), textColor: environment.theme.list.itemSecondaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.18 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + if let textView = self.text.view { + if textView.superview == nil { + self.addSubview(textView) + } + transition.setFrame(view: textView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: contentHeight), size: textSize)) + } + contentHeight += textSize.height + contentHeight += 12.0 + + contentHeight += 32.0 + + var buttonContents: [AnyComponentWithIdentity] = [] + buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( + Text(text: environment.strings.Story_UpgradeQuality_Action, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) + ))) + + buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(ButtonSubtitleComponent( + title: environment.strings.Story_UpgradeQuality_ActionSubtitle, + color: environment.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7) + )))) + + let buttonSize = self.button.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + color: environment.theme.list.itemCheckColors.fillColor, + foreground: environment.theme.list.itemCheckColors.foregroundColor, + pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( + VStack(buttonContents, spacing: 3.0) + )), + isEnabled: true, + allowActionWhenDisabled: true, + displaysProgress: false, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + + component.action() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + ) + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + transition.setFrame(view: buttonView, frame: buttonFrame) + } + contentHeight += buttonSize.height + + if environment.safeInsets.bottom.isZero { + contentHeight += 16.0 + } else { + contentHeight += environment.safeInsets.bottom + 14.0 + } + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + 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 final class StoryQualityUpgradeSheetScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let buttonAction: (() -> Void)? + + init( + context: AccountContext, + buttonAction: (() -> Void)? + ) { + self.context = context + self.buttonAction = buttonAction + } + + static func ==(lhs: StoryQualityUpgradeSheetScreenComponent, rhs: StoryQualityUpgradeSheetScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>() + private let sheetAnimateOut = ActionSlot>() + + private var component: StoryQualityUpgradeSheetScreenComponent? + private var environment: EnvironmentType? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: StoryQualityUpgradeSheetScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environment + + let sheetEnvironment = SheetComponentEnvironment( + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { [weak self] _ in + guard let self, let environment = self.environment else { + return + } + self.sheetAnimateOut.invoke(Action { _ in + if let controller = environment.controller() { + controller.dismiss(completion: nil) + } + }) + } + ) + let _ = self.sheet.update( + transition: transition, + component: AnyComponent(SheetComponent( + content: AnyComponent(StoryQualityUpgradeSheetContentComponent( + action: { [weak self] in + guard let self else { + return + } + self.sheetAnimateOut.invoke(Action { [weak self] _ in + if let controller = environment.controller() { + controller.dismiss(completion: nil) + } + + guard let self else { + return + } + self.component?.buttonAction?() + }) + }, + dismiss: { + self.sheetAnimateOut.invoke(Action { _ in + if let controller = environment.controller() { + controller.dismiss(completion: nil) + } + }) + } + )), + backgroundColor: .color(environment.theme.overallDarkAppearance ? environment.theme.list.itemBlocksBackgroundColor : environment.theme.list.blocksBackgroundColor), + animateOut: self.sheetAnimateOut + )), + environment: { + environment + sheetEnvironment + }, + containerSize: availableSize + ) + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } + + 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) + } +} + +public class StoryQualityUpgradeSheetScreen: ViewControllerComponentContainer { + public init( + context: AccountContext, + buttonAction: (() -> Void)? = nil + ) { + super.init(context: context, component: StoryQualityUpgradeSheetScreenComponent( + context: context, + buttonAction: buttonAction + ), navigationBarAppearance: .none, theme: .dark) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.view.disablesInteractiveModalDismiss = true + } + + override public func dismiss(completion: (() -> Void)? = nil) { + super.dismiss(completion: { + completion?() + }) + self.wasDismissed?() + } +} diff --git a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift index f384ad05c07..f83e5aaebab 100644 --- a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift +++ b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift @@ -357,19 +357,69 @@ public class ImmediateTextNodeWithEntities: TextNode { public var tapAttributeAction: (([NSAttributedString.Key: Any], Int) -> Void)? public var longTapAttributeAction: (([NSAttributedString.Key: Any], Int) -> Void)? + public var customItemLayout: ((CGSize, TelegramMediaFile) -> CGSize)? + private func processedAttributedText() -> NSAttributedString? { var updatedString: NSAttributedString? if let sourceString = self.attributedText { let string = NSMutableAttributedString(attributedString: sourceString) - let fullRange = NSRange(location: 0, length: string.length) - string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, _ in - if let value = value as? ChatTextInputTextCustomEmojiAttribute { - if let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont { - string.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: range) + var fullRange = NSRange(location: 0, length: string.length) + var originalTextId = 0 + while true { + var found = false + string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, stop in + if let value = value as? ChatTextInputTextCustomEmojiAttribute, let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont { + let updatedSubstring = NSMutableAttributedString(string: "&") + + let replacementRange = NSRange(location: 0, length: updatedSubstring.length) + updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange) + updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange) + updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange) + originalTextId += 1 + + let itemSize = (font.pointSize * 24.0 / 17.0) + + let runDelegateData = RunDelegateData( + ascent: font.ascender, + descent: font.descender, + width: itemSize + ) + var callbacks = CTRunDelegateCallbacks( + version: kCTRunDelegateCurrentVersion, + dealloc: { dataRef in + Unmanaged.fromOpaque(dataRef).release() + }, + getAscent: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().ascent + }, + getDescent: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().descent + }, + getWidth: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().width + } + ) + + if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) { + updatedSubstring.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: replacementRange) + } + + string.replaceCharacters(in: range, with: updatedSubstring) + let updatedRange = NSRange(location: range.location, length: updatedSubstring.length) + + found = true + stop.pointee = ObjCBool(true) + fullRange = NSRange(location: updatedRange.upperBound, length: fullRange.upperBound - range.upperBound) } + }) + if !found { + break } - }) + } updatedString = string } @@ -418,16 +468,20 @@ public class ImmediateTextNodeWithEntities: TextNode { let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index) validIds.append(id) - let itemSize = floor(stickerItem.fontSize * 24.0 / 17.0) + let itemSide = floor(stickerItem.fontSize * 24.0 / 17.0) + var itemSize = CGSize(width: itemSide, height: itemSide) + if let file = stickerItem.file, let customItemLayout = self.customItemLayout { + itemSize = customItemLayout(itemSize, file) + } - let itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 0.0).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0) + let itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 0.0).center, size: CGSize()).insetBy(dx: -itemSize.width / 2.0, dy: -itemSize.height / 2.0) let itemLayer: InlineStickerItemLayer if let current = self.inlineStickerItemLayers[id] { itemLayer = current itemLayer.dynamicColor = item.textColor } else { - let pointSize = floor(itemSize * 1.3) + let pointSize = floor(itemSize.width * 1.3) itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: false, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor) self.inlineStickerItemLayers[id] = itemLayer self.layer.addSublayer(itemLayer) diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index ad68560a448..f76d44fcc0d 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -1,3 +1,6 @@ +// MARK: Nicegram (useRearCamTelescopy) +import NGData +// import Foundation import UIKit import Display @@ -620,9 +623,16 @@ public class VideoMessageCameraScreen: ViewController { self.previewContainerView = UIView() self.previewContainerView.clipsToBounds = true - + let isDualCameraEnabled = Camera.isDualCameraSupported - let isFrontPosition = "".isEmpty + // MARK: Nicegram (useRearCamTelescopy), change let to var + var isFrontPosition = "".isEmpty + + // MARK: Nicegram (useRearCamTelescopy) + if NGSettings.useRearCamTelescopy { + isFrontPosition = false + } + // self.mainPreviewView = CameraSimplePreviewView(frame: .zero, main: true, roundVideo: true) self.additionalPreviewView = CameraSimplePreviewView(frame: .zero, main: false, roundVideo: true) @@ -695,7 +705,7 @@ public class VideoMessageCameraScreen: ViewController { func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) { let previewReady: Signal if #available(iOS 13.0, *) { - previewReady = self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing |> delay(0.2, queue: Queue.mainQueue()) + previewReady = self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing |> delay(0.25, queue: Queue.mainQueue()) } else { previewReady = .single(true) |> delay(0.35, queue: Queue.mainQueue()) } @@ -740,7 +750,7 @@ public class VideoMessageCameraScreen: ViewController { position: self.cameraState.position, isDualEnabled: self.cameraState.isDualCameraEnabled, audio: true, - photo: true, + photo: false, metadata: false, isRoundVideo: true ), diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/Contents.json index 4444d5f4125..68cff310a4a 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "IconDownloadLocked.svg", + "filename" : "downloadlocked_24.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/IconDownloadLocked.svg b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/IconDownloadLocked.svg deleted file mode 100644 index 83f499ad6ee..00000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/IconDownloadLocked.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/downloadlocked_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/downloadlocked_24.pdf new file mode 100644 index 00000000000..5cdde044a24 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DownloadLocked.imageset/downloadlocked_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/Contents.json index 98ff298312e..067fda00676 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "IconEyeLocked.svg", + "filename" : "eyelocked_24.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/IconEyeLocked.svg b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/IconEyeLocked.svg deleted file mode 100644 index b40fc127aab..00000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/IconEyeLocked.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/eyelocked_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/eyelocked_24.pdf new file mode 100644 index 00000000000..138d32e0e3a Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/EyeLocked.imageset/eyelocked_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHd.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHd.imageset/Contents.json new file mode 100644 index 00000000000..c8162f9aab8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHd.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "hd_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHd.imageset/hd_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHd.imageset/hd_24.pdf new file mode 100644 index 00000000000..18c12cc6bab Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHd.imageset/hd_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHdLocked.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHdLocked.imageset/Contents.json new file mode 100644 index 00000000000..69aa80e773a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHdLocked.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "hdlocked_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHdLocked.imageset/hdlocked_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHdLocked.imageset/hdlocked_24.pdf new file mode 100644 index 00000000000..4d88e227242 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualityHdLocked.imageset/hdlocked_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualitySd.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualitySd.imageset/Contents.json new file mode 100644 index 00000000000..19b630a4ff6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualitySd.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "sd_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualitySd.imageset/sd_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualitySd.imageset/sd_24.pdf new file mode 100644 index 00000000000..156b9a1ad80 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/QualitySd.imageset/sd_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/Contents.json new file mode 100644 index 00000000000..d2403414fea --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tag_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/tag_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/tag_24.pdf new file mode 100644 index 00000000000..e962456c80f Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/tag_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagEditName.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagEditName.imageset/Contents.json new file mode 100644 index 00000000000..0bb5576c53e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagEditName.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagname_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagEditName.imageset/tagname_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagEditName.imageset/tagname_24.pdf new file mode 100644 index 00000000000..211013e8f26 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagEditName.imageset/tagname_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/Contents.json new file mode 100644 index 00000000000..9cdaca5f2d3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagfilter_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/tagfilter_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/tagfilter_24.pdf new file mode 100644 index 00000000000..69572192e29 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/tagfilter_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/Contents.json new file mode 100644 index 00000000000..7d5bd6818e7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagcrossed_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/tagcrossed_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/tagcrossed_24.pdf new file mode 100644 index 00000000000..ed941d07ea6 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/tagcrossed_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagEditIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagEditIcon.imageset/Contents.json new file mode 100644 index 00000000000..e204641a839 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagEditIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagedit_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagEditIcon.imageset/tagedit_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagEditIcon.imageset/tagedit_30.pdf new file mode 100644 index 00000000000..e462a6c5fce Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagEditIcon.imageset/tagedit_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagIcon.imageset/Contents.json new file mode 100644 index 00000000000..eb54823de12 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagadd_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagIcon.imageset/tagadd_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagIcon.imageset/tagadd_30.pdf new file mode 100644 index 00000000000..a22326543eb Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/TagIcon.imageset/tagadd_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/Contents.json index 9712686f35d..4551917a5ea 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/Contents.json @@ -1,12 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "ic_calendar.pdf" + "filename" : "calendarsearch_30.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/calendarsearch_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/calendarsearch_30.pdf new file mode 100644 index 00000000000..b0b9b9376cf Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/calendarsearch_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/ic_calendar.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/ic_calendar.pdf deleted file mode 100644 index 8a6c808dc1a..00000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Calendar.imageset/ic_calendar.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/Contents.json index 05df939c0cf..6a378c4396e 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/Contents.json @@ -1,12 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "ic_member.pdf" + "filename" : "personsearch_30.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/ic_member.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/ic_member.pdf deleted file mode 100644 index 910e8dafbeb..00000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/ic_member.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/personsearch_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/personsearch_30.pdf new file mode 100644 index 00000000000..e7dd2050ed2 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Search/Members.imageset/personsearch_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/NavigationSearchTagsIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/NavigationSearchTagsIcon.imageset/Contents.json new file mode 100644 index 00000000000..c384c06593e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/NavigationSearchTagsIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "savedsearch_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/NavigationSearchTagsIcon.imageset/savedsearch_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/NavigationSearchTagsIcon.imageset/savedsearch_30.pdf new file mode 100644 index 00000000000..99599cad122 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/NavigationSearchTagsIcon.imageset/savedsearch_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/ChatSearchPanelTag.svg b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/ChatSearchPanelTag.svg deleted file mode 100644 index d09d68b136c..00000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/ChatSearchPanelTag.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/Contents.json index d9233184278..1697034325d 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ChatSearchPanelTag.svg", + "filename" : "tag.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/tag.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/tag.pdf new file mode 100644 index 00000000000..f32a91e739a Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SearchTagTab.imageset/tag.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Item List/PremiumIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/PremiumIcon.imageset/Contents.json new file mode 100644 index 00000000000..f34bc3513de --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/PremiumIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "premiumstar_16.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Item List/PremiumIcon.imageset/premiumstar_16.pdf b/submodules/TelegramUI/Images.xcassets/Item List/PremiumIcon.imageset/premiumstar_16.pdf new file mode 100644 index 00000000000..117a2e4bfb1 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Item List/PremiumIcon.imageset/premiumstar_16.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/Contents.json new file mode 100644 index 00000000000..1e34c1570c0 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tag_30 (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/tag_30 (2).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/tag_30 (2).pdf new file mode 100644 index 00000000000..a815495248f Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Perk/MessageTags.imageset/tag_30 (2).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/Contents.json new file mode 100644 index 00000000000..8f8aeb69e95 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "hd_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/hd_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/hd_30.pdf new file mode 100644 index 00000000000..fd38ff5908e Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stories/Quality.imageset/hd_30.pdf differ diff --git a/submodules/TelegramUI/Resources/Animations/StoryUpgradeSheet.tgs b/submodules/TelegramUI/Resources/Animations/StoryUpgradeSheet.tgs new file mode 100644 index 00000000000..d57e811cc8e Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/StoryUpgradeSheet.tgs differ diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 5d63357687a..93a5c63a840 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1813,6 +1813,8 @@ private class UserInterfaceStyleObserverWindow: UIWindow { } }) + //self.addBackgroundDownloadTask() + return true } @@ -1947,7 +1949,21 @@ private class UserInterfaceStyleObserverWindow: UIWindow { task.resume() } + private func addBackgroundDownloadTask() { + let baseAppBundleId = Bundle.main.bundleIdentifier! + let session = self.urlSession(identifier: "\(baseAppBundleId).backroundSession") + + var request = URLRequest(url: URL(string: "https://example.com/\(UInt64.random(in: 0 ... UInt64.max))")!) + request.httpMethod = "GET" + + let task = session.downloadTask(with: request) + Logger.shared.log("App \(self.episodeId)", "adding download task \(String(describing: request.url))") + task.earliestBeginDate = Date(timeIntervalSinceNow: 30.0) + task.resume() + } + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + Logger.shared.log("App \(self.episodeId)", "completed download task \(String(describing: task.originalRequest?.url)) error: \(String(describing: error))") if let response = task.response as? HTTPURLResponse { if let originalRequest = task.originalRequest { if let requestResourceId = originalRequest.value(forHTTPHeaderField: "tresource") { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index 84fa6ee7955..1d1f05ea90f 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -15,6 +15,8 @@ import EntityKeyboard import TextNodeWithEntities import PremiumUI import TooltipUI +import TopMessageReactions +import TelegramNotices extension ChatControllerImpl { func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void { @@ -107,14 +109,21 @@ extension ChatControllerImpl { actions.context = self.context actions.animationCache = self.controllerInteraction?.presentationContext.animationCache - - if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty { + + // MARK: Nicegram HideReactions, account added + if canAddMessageReactions(message: topMessage, account: context.account), let allowedReactions = allowedReactions, !topReactions.isEmpty { actions.reactionItems = topReactions.map(ReactionContextItem.reaction) + actions.selectedReactionItems = selectedReactions.reactions if message.areReactionsTags(accountPeerId: self.context.account.peerId) { - actions.reactionsTitle = presentationData.strings.Chat_ContextMenuTagsTitle + if self.presentationInterfaceState.isPremium { + actions.reactionsTitle = presentationData.strings.Chat_ContextMenuTagsTitle + } else { + actions.reactionsTitle = presentationData.strings.Chat_MessageContextMenu_NonPremiumTagsTitle + actions.reactionsLocked = true + actions.selectedReactionItems = Set() + } actions.allPresetReactionsAreAvailable = true } - actions.selectedReactionItems = selectedReactions.reactions if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info { actions.alwaysAllowPremiumReactions = true @@ -158,7 +167,7 @@ extension ChatControllerImpl { animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, - subject: .reaction(onlyTop: false), + subject: message.areReactionsTags(accountPeerId: self.context.account.peerId) ? .messageTag : .reaction(onlyTop: false), hasTrending: false, topReactionItems: reactionItems, areUnicodeEmojiEnabled: false, @@ -305,17 +314,7 @@ extension ChatControllerImpl { } controller?.dismissWithoutContent() - - let context = self.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, action: { - let controller = PremiumIntroScreen(context: context, source: .reactions) - replaceImpl?(controller) - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - self.push(controller) + self.presentTagPremiumPaywall() } controller.reactionSelected = { [weak self, weak controller] chosenUpdatedReaction, isLarge in @@ -387,13 +386,32 @@ extension ChatControllerImpl { standaloneReactionAnimation.frame = self.chatDisplayNode.bounds self.chatDisplayNode.addSubnode(standaloneReactionAnimation) }, completion: { [weak self, weak itemNode, weak targetView] in - guard let self, let itemNode = itemNode, let targetView = targetView else { + guard let self, let itemNode, let targetView else { return } - let _ = self - let _ = itemNode - let _ = targetView + if self.chatLocation.peerId == self.context.account.peerId { + let _ = (ApplicationSpecificNotice.getSavedMessageTagLabelSuggestion(accountManager: self.context.sharedContext.accountManager) + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak targetView, weak itemNode] value in + guard let self, let targetView, let itemNode else { + return + } + if value >= 3 { + return + } + + let _ = itemNode + + let rect = self.chatDisplayNode.view.convert(targetView.bounds, from: targetView).insetBy(dx: -8.0, dy: -8.0) + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Chat_TooltipAddTagLabel), location: .point(rect, .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _, _ in + return .dismiss(consume: false) + }) + self.present(tooltipScreen, in: .current) + + let _ = ApplicationSpecificNotice.incrementSavedMessageTagLabelSuggestion(accountManager: self.context.sharedContext.accountManager).startStandalone() + }) + } }) } else { controller.dismiss() @@ -421,7 +439,7 @@ extension ChatControllerImpl { } } - let _ = updateMessageReactionsInteractively(account: self.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: isLarge, storeAsRecentlyUsed: true).startStandalone() + let _ = updateMessageReactionsInteractively(account: self.context.account, messageIds: [message.id], reactions: mappedUpdatedReactions, isLarge: isLarge, storeAsRecentlyUsed: true).startStandalone() } self.forEachController({ controller in diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 0bc700c38ed..0568435047f 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -397,6 +397,28 @@ func updateChatPresentationInterfaceStateImpl( selfController.updateNextChannelToReadVisibility() } + if updatedChatPresentationInterfaceState.displayHistoryFilterAsList { + var canDisplayAsList = false + if updatedChatPresentationInterfaceState.search != nil { + if updatedChatPresentationInterfaceState.search?.resultsState != nil { + canDisplayAsList = true + } + if updatedChatPresentationInterfaceState.historyFilter != nil { + canDisplayAsList = true + } + if selfController.alwaysShowSearchResultsAsList { + canDisplayAsList = true + } + if case .peer(selfController.context.account.peerId) = updatedChatPresentationInterfaceState.chatLocation { + canDisplayAsList = true + } + } + + if !canDisplayAsList { + updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedDisplayHistoryFilterAsList(false) + } + } + selfController.presentationInterfaceState = updatedChatPresentationInterfaceState selfController.updateSlowmodeStatus() @@ -408,6 +430,8 @@ func updateChatPresentationInterfaceStateImpl( selfController.chatDisplayNode.collapseInput() } + selfController.tempHideAccessoryPanels = selfController.presentationInterfaceState.search != nil + if selfController.isNodeLoaded { selfController.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, transition: transition, interactive: interactive, completion: completion) } else { @@ -452,23 +476,58 @@ func updateChatPresentationInterfaceStateImpl( selfController.leftNavigationButton = nil } + var buttonsAnimated = transition.isAnimated if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) { if selfController.rightNavigationButton != button { - var animated = transition.isAnimated if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action { - animated = false + buttonsAnimated = false } if case .replyThread = selfController.chatLocation { - animated = false + buttonsAnimated = false } - selfController.navigationItem.setRightBarButton(button.buttonItem, animated: animated) selfController.rightNavigationButton = button } } else if let _ = selfController.rightNavigationButton { - selfController.navigationItem.setRightBarButton(nil, animated: transition.isAnimated) selfController.rightNavigationButton = nil } + if let button = secondaryRightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.secondaryRightNavigationButton, target: selfController, selector: #selector(selfController.secondaryRightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) { + if selfController.secondaryRightNavigationButton != button { + if let currentButton = selfController.secondaryRightNavigationButton?.action, currentButton == button.action { + buttonsAnimated = false + } + if case .replyThread = selfController.chatLocation { + buttonsAnimated = false + } + selfController.secondaryRightNavigationButton = button + } + } else if let _ = selfController.secondaryRightNavigationButton { + selfController.secondaryRightNavigationButton = nil + } + + var rightBarButtons: [UIBarButtonItem] = [] + if let rightNavigationButton = selfController.rightNavigationButton { + rightBarButtons.append(rightNavigationButton.buttonItem) + } + if let secondaryRightNavigationButton = selfController.secondaryRightNavigationButton { + rightBarButtons.append(secondaryRightNavigationButton.buttonItem) + } + var rightBarButtonsUpdated = false + let currentRightBarButtons = selfController.navigationItem.rightBarButtonItems ?? [] + if rightBarButtons.count != currentRightBarButtons.count { + rightBarButtonsUpdated = true + } else { + for i in 0 ..< rightBarButtons.count { + if rightBarButtons[i] !== currentRightBarButtons[i] { + rightBarButtonsUpdated = true + break + } + } + } + if rightBarButtonsUpdated { + selfController.navigationItem.setRightBarButtonItems(rightBarButtons, animated: buttonsAnimated) + } + if let controllerInteraction = selfController.controllerInteraction { if updatedChatPresentationInterfaceState.interfaceState.selectionState != controllerInteraction.selectionState { controllerInteraction.selectionState = updatedChatPresentationInterfaceState.interfaceState.selectionState @@ -514,9 +573,24 @@ func updateChatPresentationInterfaceStateImpl( selfController.presentationInterfaceStatePromise.set(selfController.presentationInterfaceState) - if let historyFilter = selfController.presentationInterfaceState.historyFilter, historyFilter.isActive, !historyFilter.customTags.isEmpty { - selfController.chatDisplayNode.historyNode.updateTag(tag: .customTag(historyFilter.customTags[0])) + if case .tag = selfController.chatDisplayNode.historyNode.tag { } else { - selfController.chatDisplayNode.historyNode.updateTag(tag: nil) + if let historyFilter = selfController.presentationInterfaceState.historyFilter { + selfController.chatDisplayNode.historyNode.updateTag(tag: .customTag(historyFilter.customTag)) + } else { + selfController.chatDisplayNode.historyNode.updateTag(tag: nil) + } } + + selfController.updateDownButtonVisibility() + + if case .standard(.embedded) = selfController.presentationInterfaceState.mode, let controllerInteraction = selfController.controllerInteraction, let interfaceInteraction = selfController.interfaceInteraction { + if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(selfController.presentationInterfaceState, context: selfController.context, currentPanel: selfController.customNavigationPanelNode as? ChatTitleAccessoryPanelNode, controllerInteraction: controllerInteraction, interfaceInteraction: interfaceInteraction, force: true) { + selfController.customNavigationPanelNode = titleAccessoryPanelNode as? ChatControllerCustomNavigationPanelNode + } else { + selfController.customNavigationPanelNode = nil + } + } + + selfController.stateUpdated?(transition) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4949776556d..301ef9b7473 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -135,6 +135,8 @@ import MediaEditorScreen import WallpaperGalleryScreen import WallpaperGridScreen import VideoMessageCameraScreen +import TopMessageReactions +import PeerInfoScreen public enum ChatControllerPeekActions { case standard @@ -280,6 +282,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var chatTitleView: ChatTitleView? var leftNavigationButton: ChatNavigationButton? var rightNavigationButton: ChatNavigationButton? + var secondaryRightNavigationButton: ChatNavigationButton? var chatInfoNavigationButton: ChatNavigationButton? var moreBarButton: MoreHeaderButton @@ -316,6 +319,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var sendAsPeersDisposable: Disposable? var preloadAttachBotIconsDisposables: DisposableSet? var keepMessageCountersSyncrhonizedDisposable: Disposable? + var keepSavedMessagesSyncrhonizedDisposable: Disposable? var saveMediaDisposable: MetaDisposable? var giveawayStatusDisposable: MetaDisposable? var nameColorDisposable: Disposable? @@ -465,6 +469,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G weak var slowmodeTooltipController: ChatSlowmodeHintController? weak var currentContextController: ContextController? + public var visibleContextController: ViewController? { + return self.currentContextController + } weak var sendMessageActionsController: ChatSendMessageActionSheetController? var searchResultsController: ChatSearchResultsController? @@ -519,6 +526,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G weak var currentWebAppController: ViewController? weak var currentImportMessageTooltip: UndoOverlayController? + + public var customNavigationBarContentNode: NavigationBarContentNode? + public var customNavigationPanelNode: ChatControllerCustomNavigationPanelNode? + public var stateUpdated: ((ContainedViewLayoutTransition) -> Void)? public override var customData: Any? { return self.chatLocation @@ -584,6 +595,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)? var performOpenURL: ((Message?, String, Promise?) -> Void)? + public var alwaysShowSearchResultsAsList: Bool = false { + didSet { + self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) + self.chatDisplayNode.alwaysShowSearchResultsAsList = self.alwaysShowSearchResultsAsList + } + } + public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(.default), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) { let _ = ChatControllerCount.modify { value in return value + 1 @@ -1087,8 +1105,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let openChatLocation = strongSelf.chatLocation + var chatFilterTag: MemoryBuffer? + if case let .customTag(value) = strongSelf.chatDisplayNode.historyNode.tag { + chatFilterTag = value + } - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: openChatLocation, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: openChatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -1276,7 +1298,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } self.openMessageReactionContextMenu(message: message, sourceView: sourceView, gesture: gesture, value: value) - }, updateMessageReaction: { [weak self] initialMessage, reaction in + }, updateMessageReaction: { [weak self] initialMessage, reaction, force in guard let strongSelf = self else { return } @@ -1286,10 +1308,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let message = messages.first else { return } + if case .default = reaction, strongSelf.chatLocation.peerId == strongSelf.context.account.peerId { + return + } - let _ = (peerMessageAllowedReactions(context: strongSelf.context, message: message) - |> deliverOnMainQueue).startStandalone(next: { allowedReactions in - guard let strongSelf = self else { + if !force && message.areReactionsTags(accountPeerId: strongSelf.context.account.peerId) { + if case .pinnedMessages = strongSelf.subject { + return + } + + if !strongSelf.presentationInterfaceState.isPremium { + strongSelf.presentTagPremiumPaywall() return } @@ -1301,13 +1330,57 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - if !canAddMessageReactions(message: message) { - itemNode.openMessageContextMenu() + let chosenReaction: MessageReaction.Reaction? + + switch reaction { + case .default: + switch item.associatedData.defaultReaction { + case .none: + chosenReaction = nil + case let .builtin(value): + chosenReaction = .builtin(value) + case let .custom(fileId): + chosenReaction = .custom(fileId) + } + case let .reaction(value): + switch value { + case let .builtin(value): + chosenReaction = .builtin(value) + case let .custom(fileId): + chosenReaction = .custom(fileId) + } + } + + guard let chosenReaction = chosenReaction else { return } - if strongSelf.context.sharedContext.immediateExperimentalUISettings.disableQuickReaction { - itemNode.openMessageContextMenu() + let tag = ReactionsMessageAttribute.messageTag(reaction: chosenReaction) + if strongSelf.presentationInterfaceState.historyFilter?.customTag == tag { + strongSelf.interfaceInteraction?.updateHistoryFilter { _ in + return nil + } + } else { + strongSelf.chatDisplayNode.historyNode.frozenMessageForScrollingReset = message.id + strongSelf.interfaceInteraction?.updateHistoryFilter { _ in + return ChatPresentationInterfaceState.HistoryFilter(customTag: tag) + } + } + } + return + } + + let _ = (peerMessageAllowedReactions(context: strongSelf.context, message: message) + |> deliverOnMainQueue).startStandalone(next: { allowedReactions in + guard let strongSelf = self else { + return + } + + strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in + guard let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item else { + return + } + guard item.message.id == message.id else { return } @@ -1351,6 +1424,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if removedReaction == nil { + // MARK: Nicegram HideReactions, account added + if !canAddMessageReactions(message: message, account: context.account) { + itemNode.openMessageContextMenu() + return + } + + if strongSelf.context.sharedContext.immediateExperimentalUISettings.disableQuickReaction { + itemNode.openMessageContextMenu() + return + } + guard let allowedReactions = allowedReactions else { itemNode.openMessageContextMenu() return @@ -1508,7 +1592,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: false, storeAsRecentlyUsed: false).startStandalone() + let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageIds: [message.id], reactions: mappedUpdatedReactions, isLarge: false, storeAsRecentlyUsed: false).startStandalone() } }) }, activateMessagePinch: { [weak self] sourceNode in @@ -2281,129 +2365,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return $0.updatedInputMode(f) }) }, openMessageShareMenu: { [weak self] id in - if let strongSelf = self, let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(id), let message = messages.first { - let chatPresentationInterfaceState = strongSelf.presentationInterfaceState - var warnAboutPrivate = false - var canShareToStory = false - if case .peer = chatPresentationInterfaceState.chatLocation, let channel = message.peers[message.id.peerId] as? TelegramChannel { - if case .broadcast = channel.info { - canShareToStory = true - } - if channel.addressName == nil { - warnAboutPrivate = true - } - } - let shareController = ShareController(context: strongSelf.context, subject: .messages(messages), updatedPresentationData: strongSelf.updatedPresentationData, shareAsLink: true) - - shareController.parentNavigationController = strongSelf.navigationController as? NavigationController - - if let message = messages.first, message.media.contains(where: { media in - if media is TelegramMediaContact || media is TelegramMediaPoll { - return true - } else if let file = media as? TelegramMediaFile, file.isSticker || file.isAnimatedSticker || file.isVideoSticker { - return true - } else { - return false - } - }) { - canShareToStory = false - } - if message.text.containsOnlyEmoji { - canShareToStory = false - } - - if canShareToStory { - shareController.shareStory = { [weak self] in - guard let self else { - return - } - Queue.mainQueue().after(0.15) { - self.openStorySharing(messages: messages) - } - } - } - shareController.openShareAsImage = { [weak self] messages in - if let strongSelf = self { - strongSelf.present(ChatQrCodeScreen(context: strongSelf.context, subject: .messages(messages)), in: .window(.root)) - } - } - shareController.dismissed = { [weak self] shared in - if shared { - self?.commitPurposefulAction() - } - } - shareController.actionCompleted = { [weak self] in - if let strongSelf = self { - let content: UndoOverlayContent - if warnAboutPrivate { - content = .linkCopied(text: strongSelf.presentationData.strings.Conversation_PrivateMessageLinkCopiedLong) - } else { - content = .linkCopied(text: strongSelf.presentationData.strings.Conversation_LinkCopied) - } - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - } - shareController.completed = { [weak self] peerIds in - guard let strongSelf = self else { - return - } - - let _ = (strongSelf.context.engine.data.get( - EngineDataList( - peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) - ) - ) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in - guard let strongSelf = self else { - return - } - let peers = peerList.compactMap { $0 } - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let text: String - var savedMessages = false - if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId { - text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many - savedMessages = true - } else { - if peers.count == 1, let peer = peers.first { - var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - peerName = peerName.replacingOccurrences(of: "**", with: "") - text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string - } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { - var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "") - var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "") - text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string - } else if let peer = peers.first { - var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - peerName = peerName.replacingOccurrences(of: "**", with: "") - text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(peers.count - 1)").string - } else { - text = "" - } - } - - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in - if savedMessages, let self, action == .info { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { - return - } - guard let navigationController = self.navigationController as? NavigationController else { - return - } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) - }) - } - return false - }), in: .current) - }) - } - strongSelf.chatDisplayNode.dismissInput() - strongSelf.present(shareController, in: .window(.root), blockInteraction: true) + guard let self else { + return } + self.openMessageShareMenu(id: id) }, presentController: { [weak self] controller, arguments in self?.present(controller, in: .window(.root), with: arguments) }, presentControllerInCurrent: { [weak self] controller, arguments in @@ -2884,7 +2849,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return .none } if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation, replyThreadMessage.peerId == strongSelf.context.account.peerId { - return .none + if replyThreadMessage.threadId != strongSelf.context.account.peerId.toInt64() { + return .none + } } if case .peer = strongSelf.chatLocation, let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum) { if message.threadId == nil { @@ -4050,7 +4017,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) } - case let .join(_, joinHash): + case let .join(_, joinHash, _): self.controllerInteraction?.openJoinLink(joinHash) case let .webPage(_, url): self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: false, external: true)) @@ -4801,13 +4768,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor))) self.moreInfoNavigationButton = ChatNavigationButton(action: .toggleInfoPanel, buttonItem: UIBarButtonItem(customDisplayNode: self.moreBarButton)!) self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in - guard let self = self else { + guard let self else { return } guard case let .peer(peerId) = self.chatLocation else { return } - ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture) + + if peerId == self.context.account.peerId { + PeerInfoScreenImpl.openSavedMessagesMoreMenu(context: self.context, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture) + } else { + ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture) + } } self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) @@ -5218,9 +5190,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let hasSearchTags: Signal - if case let .peer(peerId) = self.chatLocation, peerId == context.account.peerId { + if let peerId = self.chatLocation.peerId, peerId == context.account.peerId { hasSearchTags = context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId) + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: self.chatLocation.threadId) ) |> map { tags -> Bool in return !tags.isEmpty @@ -5728,9 +5700,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) { return .single(false) } else { - return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder)) - |> map { view, _, _ in - return !view.entries.isEmpty + if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId { + return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: .peer(id: context.account.peerId), contextHolder: Atomic(value: nil))) + |> map { view, _, _ in + return !view.entries.isEmpty + } + |> distinctUntilChanged + } else { + return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder)) + |> map { view, _, _ in + return !view.entries.isEmpty + } + |> distinctUntilChanged } } } @@ -5770,9 +5751,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let hasSearchTags: Signal - if case let .peer(peerId) = self.chatLocation, peerId == context.account.peerId { + if let peerId = self.chatLocation.peerId, peerId == context.account.peerId { hasSearchTags = context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId) + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: self.chatLocation.threadId) ) |> map { tags -> Bool in return !tags.isEmpty @@ -5878,6 +5859,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return $0.updatedPeer { _ in return renderedPeer }.updatedSavedMessagesTopicPeer(savedMessagesPeer?.peer) + .updatedHasSearchTags(hasSearchTags) + .updatedHasScheduledMessages(hasScheduledMessages) }) (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: savedMessagesPeer?.peer, overrideImage: imageOverride) @@ -6682,6 +6665,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.sendAsPeersDisposable?.dispose() self.preloadAttachBotIconsDisposables?.dispose() self.keepMessageCountersSyncrhonizedDisposable?.dispose() + self.keepSavedMessagesSyncrhonizedDisposable?.dispose() self.translationStateDisposable?.dispose() self.premiumGiftSuggestionDisposable?.dispose() self.powerSavingMonitoringDisposable?.dispose() @@ -7098,6 +7082,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let currentItem = self.tempVoicePlaylistCurrentItem { self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem) } + + self.chatDisplayNode.historyNode.beganDragging = { [weak self] in + guard let self else { + return + } + if self.presentationInterfaceState.search != nil && self.presentationInterfaceState.historyFilter != nil { + self.chatDisplayNode.historyNode.addAfterTransactionsCompleted { [weak self] in + guard let self else { + return + } + + self.chatDisplayNode.dismissInput() + } + } + } self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode, isTracking in guard let strongSelf = self else { @@ -7128,7 +7127,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if isTracking { strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition) } - strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) + strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode, isRotated: strongSelf.chatDisplayNode.historyNode.rotated) + } self.chatDisplayNode.historyNode.hasPlentyOfMessagesUpdated = { [weak self] hasPlentyOfMessages in @@ -8078,7 +8078,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: transition.peerType, networkType: transition.networkType, animateIn: false, reason: transition.reason, flashIndicators: transition.flashIndicators), updateSizeAndInsets) + mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: transition.peerType, networkType: transition.networkType, animateIn: false, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: false), updateSizeAndInsets) }, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, _ in strongSelf.additionalNavigationBarBackgroundHeight = value strongSelf.additionalNavigationBarHitTestSlop = hitTestSlop @@ -8312,22 +8312,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.chatDisplayNode.navigateButtons.downPressed = { [weak self] in - if let strongSelf = self, strongSelf.isNodeLoaded { - if let messageId = strongSelf.historyNavigationStack.removeLast() { - strongSelf.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false) + guard let self else { + return + } + + if self.presentationInterfaceState.search?.resultsState != nil { + self.interfaceInteraction?.navigateMessageSearch(.later) + } else { + if let messageId = self.historyNavigationStack.removeLast() { + self.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false) } else { - if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() { - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() - } else if case .peer = strongSelf.chatLocation { - strongSelf.scrollToEndOfHistory() - } else if case .replyThread = strongSelf.chatLocation { - strongSelf.scrollToEndOfHistory() + if case .known = self.chatDisplayNode.historyNode.visibleContentOffset() { + self.chatDisplayNode.historyNode.scrollToEndOfHistory() + } else if case .peer = self.chatLocation { + self.scrollToEndOfHistory() + } else if case .replyThread = self.chatLocation { + self.scrollToEndOfHistory() } else { - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() + self.chatDisplayNode.historyNode.scrollToEndOfHistory() } } } } + self.chatDisplayNode.navigateButtons.upPressed = { [weak self] in + guard let self else { + return + } + + if self.presentationInterfaceState.search?.resultsState != nil { + self.interfaceInteraction?.navigateMessageSearch(.earlier) + } + } self.chatDisplayNode.navigateButtons.mentionsPressed = { [weak self] in if let strongSelf = self, strongSelf.isNodeLoaded, let peerId = strongSelf.chatLocation.peerId { @@ -8751,10 +8766,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { completion(.immediate) } + }, cancelMessageSelection: { [weak self] transition in + guard let self else { + return + } + self.updateChatPresentationInterfaceState(transition: transition, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) }, deleteSelectedMessages: { [weak self] in if let strongSelf = self { if let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { - strongSelf.messageContextDisposable.set((strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds) + strongSelf.messageContextDisposable.set((strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds, keepUpdated: false) |> deliverOnMainQueue).startStrict(next: { actions in if let strongSelf = self, !actions.options.isEmpty { if let banAuthor = actions.banAuthor { @@ -8868,7 +8888,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, deleteMessages: { [weak self] messages, contextController, completion in if let strongSelf = self, !messages.isEmpty { let messageIds = Set(messages.map { $0.id }) - strongSelf.messageContextDisposable.set((strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds) + strongSelf.messageContextDisposable.set((strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds, keepUpdated: false) |> deliverOnMainQueue).startStrict(next: { actions in if let strongSelf = self, !actions.options.isEmpty { if let banAuthor = actions.banAuthor { @@ -9280,7 +9300,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return state.updatedSearch(ChatSearchData(query: "", domain: .members, domainSuggestionContext: .none, resultsState: nil)) } else if let search = state.search { switch search.domain { - case .everything: + case .everything, .tag: return state case .members: return state.updatedSearch(ChatSearchData(query: "", domain: .everything, domainSuggestionContext: .none, resultsState: nil)) @@ -11016,17 +11036,57 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self else { return } - self.updateChatPresentationInterfaceState(animated: false, interactive: true, { state in - var updatedFilter = update(state.historyFilter) - if let value = updatedFilter { - if value.customTags.count == 0 { - updatedFilter = nil - } else if value.customTags.count > 1 { - updatedFilter?.customTags.removeFirst(value.customTags.count - 1) - } + + let updatedFilter = update(self.presentationInterfaceState.historyFilter) + + let apply: () -> Void = { [weak self] in + guard let self else { + return } - return state.updatedHistoryFilter(updatedFilter) + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + var state = state.updatedHistoryFilter(updatedFilter) + if updatedFilter != nil && state.search == nil { + state = state.updatedSearch(ChatSearchData()) + } + return state + }) + } + + if let updatedFilter, let reaction = ReactionsMessageAttribute.reactionFromMessageTag(tag: updatedFilter.customTag) { + let tag = updatedFilter.customTag + + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Messages.ReactionTagMessageCount(peerId: self.context.account.peerId, threadId: self.chatLocation.threadId, reaction: reaction) + ) + |> deliverOnMainQueue).start(next: { [weak self] count in + guard let self else { + return + } + + var tagSearchInputPanelNode: ChatTagSearchInputPanelNode? + if let panelNode = self.chatDisplayNode.inputPanelNode as? ChatTagSearchInputPanelNode { + tagSearchInputPanelNode = panelNode + } else if let panelNode = self.chatDisplayNode.secondaryInputPanelNode as? ChatTagSearchInputPanelNode { + tagSearchInputPanelNode = panelNode + } + + if let tagSearchInputPanelNode, let count { + tagSearchInputPanelNode.prepareSwitchToFilter(tag: tag, count: count) + } + + apply() + }) + } else { + apply() + } + }, updateDisplayHistoryFilterAsList: { [weak self] displayAsList in + guard let self else { + return + } + + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + return state.updatedDisplayHistoryFilterAsList(displayAsList) }) }, requestLayout: { [weak self] transition in if let strongSelf = self, let layout = strongSelf.validLayout { @@ -11440,9 +11500,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if self.keepMessageCountersSyncrhonizedDisposable == nil { self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: message.peerId, threadId: message.threadId).startStrict() } - } else if case .peer(self.context.account.peerId) = self.chatLocation { + } else if self.chatLocation.peerId == self.context.account.peerId { if self.keepMessageCountersSyncrhonizedDisposable == nil { - self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: self.context.account.peerId).startStrict() + if let threadId = self.chatLocation.threadId { + self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: self.context.account.peerId, threadId: threadId).startStrict() + } else { + self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: self.context.account.peerId).startStrict() + } + } + if self.keepSavedMessagesSyncrhonizedDisposable == nil { + self.keepSavedMessagesSyncrhonizedDisposable = self.context.engine.stickers.refreshSavedMessageTags(subPeerId: self.chatLocation.threadId.flatMap(PeerId.init)).startStrict() } } @@ -12272,6 +12339,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + @objc func secondaryRightNavigationButtonAction() { + if let button = self.secondaryRightNavigationButton { + self.navigationButtonAction(button.action) + } + } + @objc func moreButtonPressed() { self.moreBarButton.play() self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) @@ -15964,8 +16037,35 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func updateDownButtonVisibility() { - let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.recordedMediaPreview != nil - self.chatDisplayNode.navigateButtons.displayDownButton = self.shouldDisplayDownButton && !recordingMediaMessage + if let search = self.presentationInterfaceState.search, let results = search.resultsState { + let resultCount = results.messageIndices.count + var resultIndex: Int? + if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) { + resultIndex = index + } else { + resultIndex = nil + } + + if let resultIndex { + self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState( + up: ChatHistoryNavigationButtons.ButtonState(isEnabled: resultIndex != 0), + down: ChatHistoryNavigationButtons.ButtonState(isEnabled: resultIndex != resultCount - 1) + ) + } else { + self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState( + up: ChatHistoryNavigationButtons.ButtonState(isEnabled: false), + down: ChatHistoryNavigationButtons.ButtonState(isEnabled: false) + ) + } + } else { + let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.recordedMediaPreview != nil + + self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState( + up: nil, + down: (self.shouldDisplayDownButton && !recordingMediaMessage) ? ChatHistoryNavigationButtons.ButtonState(isEnabled: true) : nil + ) + } + // MARK: Nicegram AiChat updateAiOverlayVisibility() // @@ -16798,72 +16898,133 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func presentDeleteMessageOptions(messageIds: Set, options: ChatAvailableMessageActionOptions, contextController: ContextControllerProtocol?, completion: @escaping (ContextMenuActionResult) -> Void) { - let actionSheet = ActionSheetController(presentationData: self.presentationData) - var items: [ActionSheetItem] = [] - var personalPeerName: String? - var isChannel = false - if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser { - personalPeerName = EnginePeer(user).compactDisplayTitle - } else if let peer = self.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let associatedPeerId = peer.associatedPeerId, let user = self.presentationInterfaceState.renderedPeer?.peers[associatedPeerId] as? TelegramUser { - personalPeerName = EnginePeer(user).compactDisplayTitle - } else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info { - isChannel = true - } - - if options.contains(.cancelSending) { - items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ContextMenuCancelSending, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() - } - })) - } - - var contextItems: [ContextMenuItem] = [] - var canDisplayContextMenu = true - - var unsendPersonalMessages = false - if options.contains(.unsendPersonal) { - canDisplayContextMenu = false - items.append(ActionSheetTextItem(title: self.presentationData.strings.Chat_UnsendMyMessagesAlertTitle(personalPeerName ?? "").string)) - items.append(ActionSheetSwitchItem(title: self.presentationData.strings.Chat_UnsendMyMessages, isOn: false, action: { value in - unsendPersonalMessages = value - })) - } else if options.contains(.deleteGlobally) { - let globalTitle: String - if isChannel { - globalTitle = self.presentationData.strings.Conversation_DeleteMessagesForEveryone - } else if let personalPeerName = personalPeerName { - globalTitle = self.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string - } else { - globalTitle = self.presentationData.strings.Conversation_DeleteMessagesForEveryone + let _ = (self.context.engine.data.get( + EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:))) + ) + |> deliverOnMainQueue).start(next: { [weak self] messages in + guard let self else { + return } - contextItems.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, f in - if let strongSelf = self { - var giveaway: TelegramMediaGiveaway? - for messageId in messageIds { - if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { - if let media = message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway { - giveaway = media - break + + let actionSheet = ActionSheetController(presentationData: self.presentationData) + var items: [ActionSheetItem] = [] + var personalPeerName: String? + var isChannel = false + if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser { + personalPeerName = EnginePeer(user).compactDisplayTitle + } else if let peer = self.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let associatedPeerId = peer.associatedPeerId, let user = self.presentationInterfaceState.renderedPeer?.peers[associatedPeerId] as? TelegramUser { + personalPeerName = EnginePeer(user).compactDisplayTitle + } else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info { + isChannel = true + } + + if options.contains(.cancelSending) { + items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ContextMenuCancelSending, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) + let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() + } + })) + } + + var contextItems: [ContextMenuItem] = [] + var canDisplayContextMenu = true + + var unsendPersonalMessages = false + if options.contains(.unsendPersonal) { + canDisplayContextMenu = false + items.append(ActionSheetTextItem(title: self.presentationData.strings.Chat_UnsendMyMessagesAlertTitle(personalPeerName ?? "").string)) + items.append(ActionSheetSwitchItem(title: self.presentationData.strings.Chat_UnsendMyMessages, isOn: false, action: { value in + unsendPersonalMessages = value + })) + } else if options.contains(.deleteGlobally) { + let globalTitle: String + if isChannel { + globalTitle = self.presentationData.strings.Conversation_DeleteMessagesForEveryone + } else if let personalPeerName = personalPeerName { + globalTitle = self.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string + } else { + globalTitle = self.presentationData.strings.Conversation_DeleteMessagesForEveryone + } + contextItems.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, f in + if let strongSelf = self { + var giveaway: TelegramMediaGiveaway? + for messageId in messageIds { + if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let media = message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway { + giveaway = media + break + } + } + } + let commit = { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) + let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() + } + if let giveaway { + Queue.mainQueue().after(0.2) { + let dateString = stringForDate(timestamp: giveaway.untilDate, timeZone: .current, strings: strongSelf.presentationData.strings) + strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Chat_Giveaway_DeleteConfirmation_Title, text: strongSelf.presentationData.strings.Chat_Giveaway_DeleteConfirmation_Text(dateString).string, actions: [TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Common_Delete, action: { + commit() + }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + })], parseMarkdown: true), in: .window(.root)) + } + f(.default) + } else { + if "".isEmpty { + f(.dismissWithoutContent) + commit() + } else { + c.dismiss(completion: { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { + commit() + }) + }) } } } - let commit = { + }))) + items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() } - if let giveaway { - Queue.mainQueue().after(0.2) { - let dateString = stringForDate(timestamp: giveaway.untilDate, timeZone: .current, strings: strongSelf.presentationData.strings) - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Chat_Giveaway_DeleteConfirmation_Title, text: strongSelf.presentationData.strings.Chat_Giveaway_DeleteConfirmation_Text(dateString).string, actions: [TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Common_Delete, action: { - commit() - }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - })], parseMarkdown: true), in: .window(.root)) - } - f(.default) + })) + } + if options.contains(.deleteLocally) { + var localOptionText = self.presentationData.strings.Conversation_DeleteMessagesForMe + if self.chatLocation.peerId == self.context.account.peerId { + if case .peer(self.context.account.peerId) = self.chatLocation, messages.values.allSatisfy({ message in message?._asMessage().effectivelyIncoming(self.context.account.peerId) ?? false }) { + localOptionText = self.presentationData.strings.Chat_ConfirmationRemoveFromSavedMessages } else { + localOptionText = self.presentationData.strings.Chat_ConfirmationDeleteFromSavedMessages + } + } else if case .scheduledMessages = self.presentationInterfaceState.subject { + localOptionText = messageIds.count > 1 ? self.presentationData.strings.ScheduledMessages_DeleteMany : self.presentationData.strings.ScheduledMessages_Delete + } else { + if options.contains(.unsendPersonal) { + localOptionText = self.presentationData.strings.Chat_DeleteMessagesConfirmation(Int32(messageIds.count)) + } else if case .peer(self.context.account.peerId) = self.chatLocation { + if messageIds.count == 1 { + localOptionText = self.presentationData.strings.Conversation_Moderate_Delete + } else { + localOptionText = self.presentationData.strings.Conversation_DeleteManyMessages + } + } + } + contextItems.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, f in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) + + let commit: () -> Void = { + guard let strongSelf = self else { + return + } + let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone() + } + if "".isEmpty { f(.dismissWithoutContent) commit() @@ -16875,85 +17036,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } } - } - }))) - items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() - } - })) - } - if options.contains(.deleteLocally) { - var localOptionText = self.presentationData.strings.Conversation_DeleteMessagesForMe - if self.chatLocation.peerId == self.context.account.peerId { - localOptionText = self.presentationData.strings.Chat_ConfirmationRemoveFromSavedMessages - } else if case .scheduledMessages = self.presentationInterfaceState.subject { - localOptionText = messageIds.count > 1 ? self.presentationData.strings.ScheduledMessages_DeleteMany : self.presentationData.strings.ScheduledMessages_Delete - } else { - if options.contains(.unsendPersonal) { - localOptionText = self.presentationData.strings.Chat_DeleteMessagesConfirmation(Int32(messageIds.count)) - } else if case .peer(self.context.account.peerId) = self.chatLocation { - if messageIds.count == 1 { - localOptionText = self.presentationData.strings.Conversation_Moderate_Delete - } else { - localOptionText = self.presentationData.strings.Conversation_DeleteManyMessages - } - } - } - contextItems.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, f in - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - - let commit: () -> Void = { - guard let strongSelf = self else { - return - } + }))) + items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone() + } - - if "".isEmpty { - f(.dismissWithoutContent) - commit() - } else { - c.dismiss(completion: { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { - commit() - }) - }) - } - } - }))) - items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone() - - } - })) - } - - if canDisplayContextMenu, let contextController = contextController { - contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) - } else { - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) + })) + } - if let contextController = contextController { - contextController.dismiss(completion: { [weak self] in - self?.present(actionSheet, in: .window(.root)) - }) + if canDisplayContextMenu, let contextController = contextController { + contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) } else { - self.chatDisplayNode.dismissInput() - self.present(actionSheet, in: .window(.root)) - completion(.default) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + + if let contextController = contextController { + contextController.dismiss(completion: { [weak self] in + self?.present(actionSheet, in: .window(.root)) + }) + } else { + self.chatDisplayNode.dismissInput() + self.present(actionSheet, in: .window(.root)) + completion(.default) + } } - } + }) } func presentClearCacheSuggestion() { @@ -17423,7 +17536,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let navigationController = self.effectiveNavigationController, navigationController.topViewController == self else { return } - let controller = ChatControllerImpl(context: self.context, chatLocation: self.chatLocation, subject: .scheduledMessages) + + var mappedChatLocation = self.chatLocation + if case let .replyThread(message) = self.chatLocation, message.peerId == self.context.account.peerId { + mappedChatLocation = .peer(id: self.context.account.peerId) + } + + let controller = ChatControllerImpl(context: self.context, chatLocation: mappedChatLocation, subject: .scheduledMessages) controller.navigationPresentation = .modal navigationController.pushViewController(controller) } @@ -18143,7 +18262,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G contextController?.present(c, in: .current) } - let _ = self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .singleMessage(message.id))) + let _ = self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .singleMessage(message.id))) } func openStorySharing(messages: [Message]) { @@ -18289,64 +18408,6 @@ final class ChatControllerContextReferenceContentSource: ContextReferenceContent } } -enum AllowedReactions { - case set(Set) - case all -} - -func peerMessageAllowedReactions(context: AccountContext, message: Message) -> Signal { - if message.id.peerId == context.account.peerId { - return .single(.all) - } - - if message.containsSecretMedia { - return .single(AllowedReactions.set(Set())) - } - - return combineLatest( - context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId), - TelegramEngine.EngineData.Item.Peer.AllowedReactions(id: message.id.peerId) - ), - context.engine.stickers.availableReactions() |> take(1) - ) - |> map { data, availableReactions -> AllowedReactions? in - let (peer, allowedReactions) = data - - if let effectiveReactions = message.effectiveReactions(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)), effectiveReactions.count >= 11 { - return .set(Set(effectiveReactions.map(\.value))) - } - - switch allowedReactions { - case .unknown: - if case let .channel(channel) = peer, case .broadcast = channel.info { - if let availableReactions = availableReactions { - return .set(Set(availableReactions.reactions.map(\.value))) - } else { - return .set(Set()) - } - } - return .all - case let .known(value): - switch value { - case .all: - if case let .channel(channel) = peer, case .broadcast = channel.info { - if let availableReactions = availableReactions { - return .set(Set(availableReactions.reactions.map(\.value))) - } else { - return .set(Set()) - } - } - return .all - case let .limited(reactions): - return .set(Set(reactions)) - case .empty: - return .set(Set()) - } - } - } -} - func peerMessageSelectedReactions(context: AccountContext, message: Message) -> Signal<(reactions: Set, files: Set), NoError> { return context.engine.stickers.availableReactions() |> take(1) diff --git a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift index 8a124f32ba5..ed961d3513e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift +++ b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift @@ -11,6 +11,8 @@ import TextFormat import UndoUI import ChatInterfaceState import PremiumUI +import ReactionSelectionNode +import TopMessageReactions extension ChatControllerImpl { // MARK: Nicegram (cloud + asCopy) @@ -162,8 +164,17 @@ extension ChatControllerImpl { }) let commit: ([EnqueueMessage]) -> Void = { result in + var result = result + strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }).updatedSearch(nil) }) + var correlationIds: [Int64] = [] + for i in 0 ..< result.count { + let correlationId = Int64.random(in: Int64.min ... Int64.max) + correlationIds.append(correlationId) + result[i] = result[i].withUpdatedCorrelationId(correlationId) + } + var displayPeers: [EnginePeer] = [] for peer in peers { let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: result) @@ -226,39 +237,53 @@ extension ChatControllerImpl { } } - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in - if savedMessages, let self, action == .info { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { - return - } - guard let navigationController = self.navigationController as? NavigationController else { - return - } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) - }) + let reactionItems: Signal<[ReactionItem], NoError> + if savedMessages && messages.count > 0 { + reactionItems = tagMessageReactions(context: strongSelf.context) + } else { + reactionItems = .single([]) + } + + let _ = (reactionItems + |> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] reactionItems in + guard let strongSelf else { + return } - return false - }), in: .current) + + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, position: savedMessages && messages.count > 0 ? .top : .bottom, animateInAsReplacement: true, action: { action in + if savedMessages, let self, action == .info { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) + }) + } + return false + }, additionalView: (savedMessages && messages.count > 0) ? chatShareToSavedMessagesAdditionalView(strongSelf, reactionItems: reactionItems, correlationIds: correlationIds) : nil), in: .current) + }) } switch mode { - case .generic: - commit(result) - case .silent: - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: true) - commit(transformedMessages) - case .schedule: - strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in - if let strongSelf = self { - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime) - commit(transformedMessages) - } - }) - case .whenOnline: - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleWhenOnlineTimestamp) - commit(transformedMessages) + case .generic: + commit(result) + case .silent: + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: true) + commit(transformedMessages) + case .schedule: + strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in + if let strongSelf = self { + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime) + commit(transformedMessages) + } + }) + case .whenOnline: + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleWhenOnlineTimestamp) + commit(transformedMessages) } } controller.peerSelected = { [weak self, weak controller] peer, threadId in @@ -297,26 +322,45 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.hapticFeedback.success() } - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in - if case .info = value, let strongSelf = self { - let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) - |> deliverOnMainQueue).startStandalone(next: { peer in - guard let strongSelf = self, let peer = peer, let navigationController = strongSelf.effectiveNavigationController else { - return - } - - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil)) - }) - return true - } - return false - }), in: .current) + let reactionItems: Signal<[ReactionItem], NoError> + if messages.count > 0 { + reactionItems = tagMessageReactions(context: strongSelf.context) + } else { + reactionItems = .single([]) + } - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messages.map { message -> EnqueueMessage in + var correlationIds: [Int64] = [] + let mappedMessages = messages.map { message -> EnqueueMessage in + let correlationId = Int64.random(in: Int64.min ... Int64.max) + correlationIds.append(correlationId) // MARK: Nicegram (asCopy) - return .forward(source: message.id, threadId: nil, grouping: .auto, attributes: [], correlationId: nil, asCopy: asCopy) + return .forward(source: message.id, threadId: nil, grouping: .auto, attributes: [], correlationId: correlationId, asCopy: asCopy) + } + + let _ = (reactionItems + |> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] reactionItems in + guard let strongSelf else { + return + } + + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, position: .top, animateInAsReplacement: true, action: { [weak self] value in + if case .info = value, let strongSelf = self { + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> deliverOnMainQueue).startStandalone(next: { peer in + guard let strongSelf = self, let peer = peer, let navigationController = strongSelf.effectiveNavigationController else { + return + } + + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil, forceOpenChat: true)) + }) + return true + } + return false + }, additionalView: messages.count > 0 ? chatShareToSavedMessagesAdditionalView(strongSelf, reactionItems: reactionItems, correlationIds: correlationIds) : nil), in: .current) }) + + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: mappedMessages) |> deliverOnMainQueue).startStandalone(next: { messageIds in if let strongSelf = self { let signals: [Signal] = messageIds.compactMap({ id -> Signal? in diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 4d5a9784a48..5bc033b7cec 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -50,6 +50,9 @@ import ChatMessageTransitionNode import ChatLoadingNode import ChatRecentActionsController import UIKitRuntimeUtils +import ChatInlineSearchResultsListComponent +import ComponentDisplayAdapters +import ComponentFlow final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -150,6 +153,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let loadingNode: ChatLoadingNode private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode? + var alwaysShowSearchResultsAsList: Bool = false + private var skippedShowSearchResultsAsListAnimationOnce: Bool = false + var inlineSearchResults: ComponentView? + private var inlineSearchResultsReadyDisposable: Disposable? + private var inlineSearchResultsReady: Bool = false + var isScrollingLockedAtTop: Bool = false private var emptyNode: ChatEmptyNode? @@ -187,10 +196,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var chatTranslationPanel: ChatTranslationPanelNode? - private var inputPanelNode: ChatInputPanelNode? + private(set) var inputPanelNode: ChatInputPanelNode? private(set) var inputPanelOverscrollNode: ChatInputPanelOverscrollNode? private weak var currentDismissedInputPanelNode: ChatInputPanelNode? - private var secondaryInputPanelNode: ChatInputPanelNode? + private(set) var secondaryInputPanelNode: ChatInputPanelNode? private(set) var accessoryPanelNode: AccessoryPanelNode? private var inputContextPanelNode: ChatInputContextPanelNode? let inputContextPanelContainer: ChatControllerTitlePanelNodeContainer @@ -963,6 +972,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.openStickersDisposable?.dispose() self.displayVideoUnmuteTipDisposable?.dispose() self.inputMediaNodeDataDisposable?.dispose() + self.inlineSearchResultsReadyDisposable?.dispose() } override func didLoad() { @@ -1010,6 +1020,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if hasChatThemeScreen { return true } + return false } @@ -1066,15 +1077,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { false } let isPrivate = (peer.addressName == nil) - let isRestricted = peer.hasPornRestriction( + + let isPornChat = peer.hasPornRestriction( contentSettings: self.context.currentContentSettings.with { $0 } ) ngBannerModel.set( chatData: ChatData( isChannel: isChannel, + isPornChat: isPornChat, isPrivate: isPrivate, - isRestricted: isRestricted + unblockRequiresAnotherPhoneNumber: peer.unblockRequiresAnotherPhoneNumber() ) ) } @@ -1385,7 +1398,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var titleAccessoryPanelBackgroundHeight: CGFloat? var titleAccessoryPanelHitTestSlop: CGFloat? var extraTransition = transition - if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction) { + if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) { if self.titleAccessoryPanelNode != titleAccessoryPanelNode { dismissedTitleAccessoryPanelNode = self.titleAccessoryPanelNode self.titleAccessoryPanelNode = titleAccessoryPanelNode @@ -1529,7 +1542,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var inputPanelNodeHandlesTransition = false var dismissedInputPanelNode: ChatInputPanelNode? - var dismissedSecondaryInputPanelNode: ASDisplayNode? + var dismissedSecondaryInputPanelNode: ChatInputPanelNode? var dismissedAccessoryPanelNode: AccessoryPanelNode? var dismissedInputContextPanelNode: ChatInputContextPanelNode? var dismissedOverlayContextPanelNode: ChatInputContextPanelNode? @@ -1585,6 +1598,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { immediatelyLayoutSecondaryInputPanelAndAnimateAppearance = true self.inputPanelClippingNode.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) } + if let viewForOverlayContent = secondaryInputPanelNode.viewForOverlayContent, viewForOverlayContent.superview == nil { + self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent) + } } else { let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) @@ -1788,13 +1804,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateFrame(node: backgroundEffectNode, frame: CGRect(origin: CGPoint(), size: layout.size)) } - transition.updateFrame(node: self.backgroundNode, frame: contentBounds) + let wallpaperBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height) + + transition.updateFrame(node: self.backgroundNode, frame: wallpaperBounds) var displayMode: WallpaperDisplayMode = .aspectFill if case .regular = layout.metrics.widthClass, layout.size.height == layout.deviceMetrics.screenSize.width { displayMode = .aspectFit } - self.backgroundNode.updateLayout(size: contentBounds.size, displayMode: displayMode, transition: transition) + self.backgroundNode.updateLayout(size: wallpaperBounds.size, displayMode: displayMode, transition: transition) transition.updateBounds(node: self.historyNodeContainer, bounds: contentBounds) transition.updatePosition(node: self.historyNodeContainer, position: contentBounds.center) @@ -1928,6 +1946,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if self.dismissedAsOverlay { inputBackgroundFrame.origin.y = layout.size.height } + if case .standard(.embedded) = self.chatPresentationInterfaceState.mode { + if self.inputPanelNode == nil { + inputBackgroundFrame.origin.y = layout.size.height + } + } let additionalScrollDistance: CGFloat = 0.0 var scrollToTop = false @@ -1991,10 +2014,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var listInsets = UIEdgeInsets(top: containerInsets.bottom + contentBottomInset, left: containerInsets.right, bottom: containerInsets.top, right: containerInsets.left) let listScrollIndicatorInsets = UIEdgeInsets(top: containerInsets.bottom + inputPanelsHeight, left: containerInsets.right, bottom: containerInsets.top, right: containerInsets.left) + + var childContentInsets: UIEdgeInsets = containerInsets + childContentInsets.bottom += inputPanelsHeight + if case .standard = self.chatPresentationInterfaceState.mode { listInsets.left += layout.safeInsets.left listInsets.right += layout.safeInsets.right + childContentInsets.left += layout.safeInsets.left + childContentInsets.right += layout.safeInsets.right + if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass { listInsets.left += 6.0 listInsets.right += 6.0 @@ -2006,6 +2036,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let current = listInsets listInsets.top = current.bottom listInsets.bottom = current.top + listInsets.top += 8.0 } var displayTopDimNode = false @@ -2066,7 +2097,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } var childrenLayout = layout - childrenLayout.intrinsicInsets = UIEdgeInsets(top: listInsets.bottom, left: listInsets.right, bottom: listInsets.top, right: listInsets.left) + if self.historyNode.rotated { + childrenLayout.intrinsicInsets = UIEdgeInsets(top: listInsets.bottom, left: listInsets.right, bottom: listInsets.top, right: listInsets.left) + } else { + childrenLayout.intrinsicInsets = UIEdgeInsets(top: listInsets.top, left: listInsets.left, bottom: listInsets.bottom, right: listInsets.right) + } self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition) listViewTransaction(ListViewUpdateSizeAndInsets(size: contentBounds.size, insets: listInsets, scrollIndicatorInsets: listScrollIndicatorInsets, duration: duration, curve: curve, ensureTopInsetForOverlayHighlightedItems: ensureTopInsetForOverlayHighlightedItems), additionalScrollDistance, scrollToTop, { [weak self] in @@ -2109,7 +2144,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } if !self.historyNode.rotated { - apparentNavigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: 6.0), size: navigateButtonsSize) + apparentNavigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: insets.top + 6.0), size: navigateButtonsSize) } var isInputExpansionEnabled = false @@ -2165,11 +2200,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { inputPanelUpdateTransition = .immediate } - if case .standard(.embedded) = self.chatPresentationInterfaceState.mode { - self.inputPanelBackgroundNode.isHidden = true - self.inputPanelBackgroundSeparatorNode.isHidden = true - self.inputPanelBottomBackgroundSeparatorNode.isHidden = true - } self.inputPanelBackgroundNode.update(size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), transition: inputPanelUpdateTransition, beginWithCurrentState: true) self.inputPanelBottomBackgroundSeparatorBaseOffset = intrinsicInputPanelBackgroundNodeSize.height inputPanelUpdateTransition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: UIScreenPixel)), beginWithCurrentState: true) @@ -2231,6 +2261,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateFrame(node: secondaryInputPanelNode, frame: apparentSecondaryInputPanelFrame) transition.updateAlpha(node: secondaryInputPanelNode, alpha: 1.0) + + if let viewForOverlayContent = secondaryInputPanelNode.viewForOverlayContent { + if inputPanelNodeHandlesTransition { + viewForOverlayContent.frame = apparentSecondaryInputPanelFrame + } else { + transition.updateFrame(view: viewForOverlayContent, frame: apparentSecondaryInputPanelFrame) + } + } } if let accessoryPanelNode = self.accessoryPanelNode, let accessoryPanelFrame = accessoryPanelFrame, !accessoryPanelNode.frame.equalTo(accessoryPanelFrame) { @@ -2414,6 +2452,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { alphaCompleted = true completed() }) + + dismissedSecondaryInputPanelNode.viewForOverlayContent?.removeFromSuperview() } if let dismissedAccessoryPanelNode = dismissedAccessoryPanelNode { @@ -2570,6 +2610,232 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.updatePlainInputSeparator(transition: transition) + + var displayInlineSearch = false + if self.chatPresentationInterfaceState.displayHistoryFilterAsList { + if self.chatPresentationInterfaceState.historyFilter != nil || self.chatPresentationInterfaceState.search?.resultsState != nil { + displayInlineSearch = true + } + if self.alwaysShowSearchResultsAsList { + displayInlineSearch = true + } + if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation { + displayInlineSearch = true + } + } + + if let peerId = self.chatPresentationInterfaceState.chatLocation.peerId, displayInlineSearch { + let inlineSearchResults: ComponentView + var inlineSearchResultsTransition = Transition(transition) + if let current = self.inlineSearchResults { + inlineSearchResults = current + } else { + inlineSearchResultsTransition = inlineSearchResultsTransition.withAnimation(.none) + inlineSearchResults = ComponentView() + self.inlineSearchResults = inlineSearchResults + } + + let mappedContents: ChatInlineSearchResultsListComponent.Contents + if let _ = self.chatPresentationInterfaceState.search?.resultsState { + mappedContents = .search(query: self.chatPresentationInterfaceState.search?.query ?? "", includeSavedPeers: self.alwaysShowSearchResultsAsList) + } else if let historyFilter = self.chatPresentationInterfaceState.historyFilter { + mappedContents = .tag(historyFilter.customTag) + } else if let search = self.chatPresentationInterfaceState.search, self.alwaysShowSearchResultsAsList { + mappedContents = .search(query: search.query, includeSavedPeers: self.alwaysShowSearchResultsAsList) + } else if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation { + mappedContents = .tag(MemoryBuffer()) + } else { + mappedContents = .empty + } + + let context = self.context + let chatLocation = self.chatLocation + + let _ = inlineSearchResults.update( + transition: inlineSearchResultsTransition, + component: AnyComponent(ChatInlineSearchResultsListComponent( + context: self.context, + presentation: ChatInlineSearchResultsListComponent.Presentation( + theme: self.chatPresentationInterfaceState.theme, + strings: self.chatPresentationInterfaceState.strings, + chatListFontSize: self.chatPresentationInterfaceState.fontSize, + dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, + nameSortOrder: self.chatPresentationInterfaceState.nameDisplayOrder, + nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder + ), + peerId: peerId, + contents: mappedContents, + insets: childContentInsets, + messageSelected: { [weak self] message in + guard let self else { + return + } + self.controller?.navigateToMessage( + from: nil, + to: .index(message.index), + scrollPosition: .center(.bottom), + rememberInStack: false, + forceInCurrentChat: true, + animated: true + ) + self.controller?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + return state.updatedDisplayHistoryFilterAsList(false) + }) + }, + peerSelected: { [weak self] peer in + guard let self else { + return + } + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams( + navigationController: navigationController, + context: self.context, + chatLocation: .replyThread(ChatReplyThreadMessage( + peerId: self.context.account.peerId, + threadId: peer.id.toInt64(), + channelMessageId: nil, + isChannelPost: false, + isForumPost: false, + maxMessage: nil, + maxReadIncomingMessageId: nil, + maxReadOutgoingMessageId: nil, + unreadCount: 0, + initialFilledHoles: IndexSet(), + initialAnchor: .automatic, + isNotAvailable: false + )), + subject: nil, + keepStack: .always + )) + }, + loadTagMessages: { tag, index in + let input: ChatHistoryLocationInput + if let index { + input = ChatHistoryLocationInput( + content: .Navigation( + index: .message(index), + anchorIndex: .message(index), + count: 45, + highlight: false + ), + id: 0 + ) + } else { + input = ChatHistoryLocationInput( + content: .Initial(count: 45), + id: 0 + ) + } + + return chatHistoryViewForLocation( + input, + ignoreMessagesInTimestampRange: nil, + context: context, + chatLocation: chatLocation, + chatLocationContextHolder: Atomic(value: nil), + scheduled: false, + fixedCombinedReadStates: nil, + tag: tag.length == 0 ? nil : .customTag(tag), + appendMessagesFromTheSameGroup: false, + additionalData: [] + ) + |> mapToSignal { viewUpdate -> Signal in + switch viewUpdate { + case .Loading: + return .complete() + case let .HistoryView(view, _, _, _, _, _, _): + return .single(view) + } + } + }, + getSearchResult: { [weak self] in + guard let self, let controller = self.controller else { + return nil + } + return controller.searchResult.get() + |> map { result in + return result?.0 + } + }, + getSavedPeers: { [weak self] query in + guard let self else { + return nil + } + let strings = self.chatPresentationInterfaceState.strings + let foundLocalPeers = context.engine.messages.searchLocalSavedMessagesPeers(query: query.lowercased(), indexNameMapping: [ + context.account.peerId: [ + PeerIndexNameRepresentation.title(title: strings.DialogList_MyNotes.lowercased(), addressNames: []), + PeerIndexNameRepresentation.title(title: "my notes".lowercased(), addressNames: []) + ], + PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2666000)): [ + PeerIndexNameRepresentation.title(title: strings.ChatList_AuthorHidden.lowercased(), addressNames: []) + ] + ]) + |> map { peers -> [(EnginePeer, MessageIndex?)] in + return peers.map { peer in + return (peer, nil) + } + } + return foundLocalPeers + } + )), + environment: {}, + containerSize: layout.size + ) + if let inlineSearchResultsView = inlineSearchResults.view as? ChatInlineSearchResultsListComponent.View { + var animateIn = false + if inlineSearchResultsView.superview == nil { + animateIn = true + if !self.alwaysShowSearchResultsAsList || self.skippedShowSearchResultsAsListAnimationOnce { + inlineSearchResultsView.alpha = 0.0 + } + self.skippedShowSearchResultsAsListAnimationOnce = true + inlineSearchResultsView.layer.allowsGroupOpacity = true + self.contentContainerNode.view.insertSubview(inlineSearchResultsView, aboveSubview: self.historyNodeContainer.view) + } + inlineSearchResultsTransition.setFrame(view: inlineSearchResultsView, frame: CGRect(origin: CGPoint(), size: layout.size)) + + if animateIn { + self.inlineSearchResultsReadyDisposable = (inlineSearchResultsView.isReady + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] _ in + guard let self else { + return + } + guard let inlineSearchResultsView = self.inlineSearchResults?.view as? ChatInlineSearchResultsListComponent.View else { + return + } + if inlineSearchResultsView.alpha == 0.0 { + inlineSearchResultsView.alpha = 1.0 + + inlineSearchResultsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + inlineSearchResultsView.animateIn() + + transition.updateSublayerTransformScale(node: self.historyNodeContainer, scale: CGPoint(x: 0.95, y: 0.95)) + } + }) + } + } + } else { + if let inlineSearchResults = self.inlineSearchResults { + self.inlineSearchResults = nil + if let inlineSearchResultsView = inlineSearchResults.view as? ChatInlineSearchResultsListComponent.View { + transition.updateAlpha(layer: inlineSearchResultsView.layer, alpha: 0.0, completion: { [weak inlineSearchResultsView] _ in + inlineSearchResultsView?.removeFromSuperview() + }) + inlineSearchResultsView.animateOut() + } + transition.updateSublayerTransformScale(node: self.historyNodeContainer, scale: CGPoint(x: 1.0, y: 1.0)) + } + if let inlineSearchResultsReadyDisposable = self.inlineSearchResultsReadyDisposable { + self.inlineSearchResultsReadyDisposable = nil + inlineSearchResultsReadyDisposable.dispose() + } + self.inlineSearchResultsReady = false + } let listBottomInset = self.historyNode.insets.top if let previousListBottomInset = previousListBottomInset, listBottomInset != previousListBottomInset { @@ -2777,8 +3043,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if (isAllowedChat(peer: peer, contentSettings: context.currentContentSettings.with { $0 })) { restrictionText = nil } else if restrictionText != nil { - showUnblockButton = true - AppCache.lastSeenBlockedChatId = peer?.id.id._internalGetInt64Value() + if let peer, !peer.unblockRequiresAnotherPhoneNumber() { + showUnblockButton = true + AppCache.lastSeenBlockedChatId = peer.id.id._internalGetInt64Value() + } } } @@ -2836,6 +3104,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let _ = chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState { showNavigateButtons = false } + if chatPresentationInterfaceState.displayHistoryFilterAsList { + showNavigateButtons = false + } transition.updateAlpha(node: self.navigateButtons, alpha: showNavigateButtons ? 1.0 : 0.0) if let openStickersDisposable = self.openStickersDisposable { @@ -2858,16 +3129,23 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let _ = self.chatPresentationInterfaceState.search, let interfaceInteraction = self.interfaceInteraction { var activate = false if self.searchNavigationNode == nil { - activate = true + if !self.chatPresentationInterfaceState.hasSearchTags { + activate = true + } self.searchNavigationNode = ChatSearchNavigationContentNode(context: self.context, theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, chatLocation: self.chatPresentationInterfaceState.chatLocation, interaction: interfaceInteraction, presentationInterfaceState: self.chatPresentationInterfaceState) } - self.navigationBar?.setContentNode(self.searchNavigationNode, animated: transitionIsAnimated) + if let navigationBar = self.navigationBar { + navigationBar.setContentNode(self.searchNavigationNode, animated: transitionIsAnimated) + } else { + self.controller?.customNavigationBarContentNode = self.searchNavigationNode + } self.searchNavigationNode?.update(presentationInterfaceState: self.chatPresentationInterfaceState) if activate { self.searchNavigationNode?.activate() } } else if let _ = self.searchNavigationNode { self.searchNavigationNode = nil + self.controller?.customNavigationBarContentNode = nil self.navigationBar?.setContentNode(nil, animated: transitionIsAnimated) } @@ -3382,6 +3660,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } + if let secondaryInputPanelNode = self.secondaryInputPanelNode, let viewForOverlayContent = secondaryInputPanelNode.viewForOverlayContent { + if let result = viewForOverlayContent.hitTest(self.view.convert(point, to: viewForOverlayContent), with: event) { + return result + } + if maybeDismissOverlayContent { + viewForOverlayContent.maybeDismissContent(point: self.view.convert(point, to: viewForOverlayContent)) + } + } + return nil } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift index 16d30ed59bc..0d6dd32c6b4 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift @@ -24,11 +24,13 @@ extension ChatControllerImpl { var openClearHistory: ((Int32) -> Void)? let enableMessageRangeDeletion: Bool = peerId.namespace == Namespaces.Peer.CloudUser + + let displayMedia = self.presentationInterfaceState.historyFilter == nil let calendarScreen = CalendarMessageScreen( context: self.context, peerId: peerId, - calendarSource: self.context.engine.messages.sparseMessageCalendar(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photoOrVideo), + calendarSource: self.context.engine.messages.sparseMessageCalendar(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photoOrVideo, displayMedia: displayMedia), initialTimestamp: initialTimestamp, enableMessageRangeDeletion: enableMessageRangeDeletion, canNavigateToEmptyDays: true, @@ -37,6 +39,10 @@ extension ChatControllerImpl { c.dismiss() return } + + strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in + return state.updatedDisplayHistoryFilterAsList(false) + }) c.dismiss() diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index a8572dacda3..a45e1a7f315 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -13,51 +13,151 @@ import TooltipUI import StickerPackPreviewUI import TextNodeWithEntities import ChatPresentationInterfaceState +import SavedTagNameAlertController +import PremiumUI extension ChatControllerImpl { + func presentTagPremiumPaywall() { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: .messageTags, action: { + let controller = PremiumIntroScreen(context: context, source: .messageTags) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) + } + func openMessageReactionContextMenu(message: Message, sourceView: ContextExtractedContentContainingView, gesture: ContextGesture?, value: MessageReaction.Reaction) { if message.areReactionsTags(accountPeerId: self.context.account.peerId) { - var items: [ContextMenuItem] = [] + if !self.presentationInterfaceState.isPremium { + self.presentTagPremiumPaywall() + return + } - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_FilterByTag, icon: { _ in - return nil - }, action: { [weak self] _, a in - guard let self else { - a(.default) - return + let reactionFile: Signal + switch value { + case .builtin: + reactionFile = self.context.engine.stickers.availableReactions() + |> take(1) + |> map { availableReactions -> TelegramMediaFile? in + return availableReactions?.reactions.first(where: { $0.value == value })?.selectAnimation } - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - return state - .updatedSearch(ChatSearchData()) - .updatedHistoryFilter(ChatPresentationInterfaceState.HistoryFilter(customTags: [ReactionsMessageAttribute.messageTag(reaction: value)], isActive: true)) - }) - - a(.default) - }))) - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_RemoveTag, textColor: .destructive, icon: { _ in - return nil - }, action: { [weak self] _, a in - a(.dismissWithoutContent) - guard let self else { - return + case let .custom(fileId): + reactionFile = self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId]) + |> map { files -> TelegramMediaFile? in + return files.values.first } - self.controllerInteraction?.updateMessageReaction(message, .reaction(value)) - }))) - - self.canReadHistory.set(false) - - let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatMessageReactionContextExtractedContentSource(chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, contentView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) - controller.dismissed = { [weak self] in - self?.canReadHistory.set(true) } - self.forEachController({ controller in - if let controller = controller as? TooltipScreen { - controller.dismiss() + let _ = (combineLatest(queue: .mainQueue(), + self.context.engine.stickers.savedMessageTagData(), + reactionFile + ) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] savedMessageTags, reactionFile in + guard let self, let savedMessageTags else { + return + } + guard let reactionFile else { + return + } + + var items: [ContextMenuItem] = [] + + let tag: EngineMessage.CustomTag = ReactionsMessageAttribute.messageTag(reaction: value) + + var hasTitle = false + if let tag = savedMessageTags.tags.first(where: { $0.reaction == value }) { + if let title = tag.title, !title.isEmpty { + hasTitle = true + } + } + + let optionTitle = hasTitle ? self.presentationData.strings.Chat_EditTagTitle_TitleEdit : self.presentationData.strings.Chat_EditTagTitle_TitleSet + + items.append(.action(ContextMenuActionItem(text: optionTitle, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/TagEditName"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, a in + guard let self else { + a(.default) + return + } + c.dismiss(completion: { [weak self] in + guard let self else { + return + } + + let _ = (self.context.engine.stickers.savedMessageTagData() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] savedMessageTags in + guard let self, let savedMessageTags else { + return + } + + let reaction = value + + let promptController = savedTagNameAlertController(context: self.context, updatedPresentationData: nil, text: optionTitle, subtext: self.presentationData.strings.Chat_EditTagTitle_Text, value: savedMessageTags.tags.first(where: { $0.reaction == reaction })?.title ?? "", reaction: reaction, file: reactionFile, characterLimit: 10, apply: { [weak self] value in + guard let self else { + return + } + + if let value { + let _ = self.context.engine.stickers.setSavedMessageTagTitle(reaction: reaction, title: value.isEmpty ? nil : value).start() + } + }) + self.interfaceInteraction?.presentController(promptController, nil) + }) + }) + }))) + + if case .pinnedMessages = self.subject { + } else { + if self.presentationInterfaceState.historyFilter?.customTag != tag { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_FilterByTag, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/TagFilter"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + guard let self else { + a(.default) + return + } + self.chatDisplayNode.historyNode.frozenMessageForScrollingReset = message.id + self.interfaceInteraction?.updateHistoryFilter { _ in + return ChatPresentationInterfaceState.HistoryFilter(customTag: tag) + } + + a(.default) + }))) + } + } + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_RemoveTag, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/TagRemove"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, a in + a(.dismissWithoutContent) + guard let self else { + return + } + self.controllerInteraction?.updateMessageReaction(message, .reaction(value), true) + }))) + + self.canReadHistory.set(false) + + let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatMessageReactionContextExtractedContentSource(chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, contentView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) + controller.dismissed = { [weak self] in + self?.canReadHistory.set(true) } - return true + + self.forEachController({ controller in + if let controller = controller as? TooltipScreen { + controller.dismiss() + } + return true + }) + self.window?.presentInGlobalOverlay(controller) }) - self.window?.presentInGlobalOverlay(controller) } else { var customFileIds: [Int64] = [] if case let .custom(fileId) = value { diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift new file mode 100644 index 00000000000..191411943f2 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift @@ -0,0 +1,239 @@ +import Foundation +import TelegramPresentationData +import AccountContext +import Postbox +import TelegramCore +import SwiftSignalKit +import ContextUI +import ChatControllerInteraction +import Display +import UIKit +import UndoUI +import ShareController +import ChatQrCodeScreen +import ChatShareMessageTagView +import ReactionSelectionNode +import TopMessageReactions + +func chatShareToSavedMessagesAdditionalView(_ chatController: ChatControllerImpl, reactionItems: [ReactionItem], correlationIds: [Int64]) -> (() -> UndoOverlayControllerAdditionalView?)? { + if !chatController.presentationInterfaceState.isPremium { + return nil + } + if correlationIds.count < 1 { + return nil + } + return { [weak chatController] () -> UndoOverlayControllerAdditionalView? in + guard let chatController else { + return nil + } + return ChatShareMessageTagView(context: chatController.context, presentationData: chatController.presentationData, isSingleMessage: correlationIds.count == 1, reactionItems: reactionItems, completion: { [weak chatController] file, updateReaction in + guard let chatController else { + return + } + + let _ = (chatController.context.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: chatController.context.account.peerId, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 45, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: []) + |> map { view, _, _ -> [EngineMessage.Id] in + let messageIds = correlationIds.compactMap { correlationId in + return chatController.context.engine.messages.synchronouslyLookupCorrelationId(correlationId: correlationId) + } + if messageIds.isEmpty { + return [] + } + + let exactResult = view.entries.compactMap { entry -> EngineMessage.Id? in + if messageIds.contains(entry.message.id) { + return entry.message.id + } else { + return nil + } + } + if !exactResult.isEmpty { + return exactResult + } + + return [] + } + |> filter { !$0.isEmpty } + |> take(1) + |> timeout(5.0, queue: .mainQueue(), alternate: .single([])) + |> deliverOnMainQueue).start(next: { [weak chatController] messageIds in + guard let chatController else { + return + } + if !messageIds.isEmpty { + let _ = chatController.context.engine.messages.setMessageReactions(ids: messageIds, reactions: [updateReaction]) + + var isBuiltinReaction = false + if case .builtin = updateReaction { + isBuiltinReaction = true + } + let presentationData = chatController.context.sharedContext.currentPresentationData.with { $0 } + chatController.present(UndoOverlayController(presentationData: presentationData, content: .messageTagged(context: chatController.context, isSingleMessage: messageIds.count == 1, customEmoji: file, isBuiltinReaction: isBuiltinReaction, customUndoText: presentationData.strings.Chat_ToastMessageTagged_Action), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak chatController] action in + if (action == .info || action == .undo), let chatController { + let _ = (chatController.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: chatController.context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak chatController] peer in + guard let chatController else { + return + } + guard let peer else { + return + } + guard let navigationController = chatController.navigationController as? NavigationController else { + return + } + chatController.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: chatController.context, chatLocation: .peer(peer), forceOpenChat: true)) + }) + return false + } + return false + }), in: .current) + } + }) + }) + } +} + +extension ChatControllerImpl { + func openMessageShareMenu(id: EngineMessage.Id) { + guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(id), let message = messages.first else { + return + } + + let chatPresentationInterfaceState = self.presentationInterfaceState + var warnAboutPrivate = false + var canShareToStory = false + if case .peer = chatPresentationInterfaceState.chatLocation, let channel = message.peers[message.id.peerId] as? TelegramChannel { + if case .broadcast = channel.info { + canShareToStory = true + } + if channel.addressName == nil { + warnAboutPrivate = true + } + } + let shareController = ShareController(context: self.context, subject: .messages(messages), updatedPresentationData: self.updatedPresentationData, shareAsLink: true) + shareController.parentNavigationController = self.navigationController as? NavigationController + + if let message = messages.first, message.media.contains(where: { media in + if media is TelegramMediaContact || media is TelegramMediaPoll { + return true + } else if let file = media as? TelegramMediaFile, file.isSticker || file.isAnimatedSticker || file.isVideoSticker { + return true + } else { + return false + } + }) { + canShareToStory = false + } + if message.text.containsOnlyEmoji { + canShareToStory = false + } + + if canShareToStory { + shareController.shareStory = { [weak self] in + guard let self else { + return + } + Queue.mainQueue().after(0.15) { + self.openStorySharing(messages: messages) + } + } + } + shareController.openShareAsImage = { [weak self] messages in + guard let self else { + return + } + self.present(ChatQrCodeScreen(context: self.context, subject: .messages(messages)), in: .window(.root)) + } + shareController.dismissed = { [weak self] shared in + if shared { + self?.commitPurposefulAction() + } + } + shareController.actionCompleted = { [weak self] in + guard let self else { + return + } + let content: UndoOverlayContent + if warnAboutPrivate { + content = .linkCopied(text: self.presentationData.strings.Conversation_PrivateMessageLinkCopiedLong) + } else { + content = .linkCopied(text: self.presentationData.strings.Conversation_LinkCopied) + } + self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + shareController.enqueued = { [weak self] peerIds, correlationIds in + guard let self else { + return + } + + let _ = (self.context.engine.data.get( + EngineDataList( + peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) + ) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in + guard let self else { + return + } + let peers = peerList.compactMap { $0 } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let text: String + var savedMessages = false + if peerIds.count == 1, let peerId = peerIds.first, peerId == self.context.account.peerId { + text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many + savedMessages = true + } else { + if peers.count == 1, let peer = peers.first { + var peerName = peer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + peerName = peerName.replacingOccurrences(of: "**", with: "") + text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string + } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { + var firstPeerName = firstPeer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "") + var secondPeerName = secondPeer.id == self.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "") + text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string + } else if let peer = peers.first { + var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + peerName = peerName.replacingOccurrences(of: "**", with: "") + text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(peers.count - 1)").string + } else { + text = "" + } + } + + let reactionItems: Signal<[ReactionItem], NoError> + if savedMessages { + reactionItems = tagMessageReactions(context: self.context) + } else { + reactionItems = .single([]) + } + + let _ = (reactionItems + |> deliverOnMainQueue).startStandalone(next: { [weak self] reactionItems in + guard let self else { + return + } + + self.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, position: savedMessages ? .top : .bottom, animateInAsReplacement: !savedMessages, action: { [weak self] action in + if savedMessages, let self, action == .info { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) + }) + } + return false + }, additionalView: savedMessages ? chatShareToSavedMessagesAdditionalView(self, reactionItems: reactionItems, correlationIds: correlationIds) : nil), in: .current) + }) + }) + } + self.chatDisplayNode.dismissInput() + self.present(shareController, in: .window(.root), blockInteraction: true) + } +} diff --git a/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift b/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift index f6fcc52e1f9..75d9bbc33f9 100644 --- a/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift +++ b/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift @@ -38,15 +38,17 @@ extension ChatControllerImpl { } var reactions: [MessageReaction.Reaction]? - if !search.query.isEmpty, let historyFilter = interfaceState.historyFilter, !historyFilter.customTags.isEmpty { - reactions = historyFilter.customTags.compactMap { - ReactionsMessageAttribute.reactionFromMessageTag(tag: $0) + if !search.query.isEmpty, let historyFilter = interfaceState.historyFilter { + reactions = ReactionsMessageAttribute.reactionFromMessageTag(tag: historyFilter.customTag).flatMap { + [$0] } } switch search.domain { case .everything: derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, reactions: reactions, threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) + case let .tag(reaction): + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, reactions: reactions ?? [reaction], threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) case .members: derivedSearchState = nil case let .member(peer): diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index d7cd4e20db8..2f1bedf6377 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import Postbox import TelegramCore import TemporaryCachedPeerDataManager @@ -7,6 +8,9 @@ import AccountContext import TelegramPresentationData import ChatHistoryEntry import ChatMessageItemCommon +import TextFormat +import Markdown +import Display func chatHistoryEntriesForView( location: ChatLocation, @@ -15,6 +19,7 @@ func chatHistoryEntriesForView( includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, + includeEmbeddedSavedChatInfo: Bool, reverse: Bool, groupMessages: Bool, reverseGroupedMessages: Bool, @@ -454,6 +459,56 @@ func chatHistoryEntriesForView( } } } + if includeEmbeddedSavedChatInfo, let peerId = location.peerId { + if !view.isLoading && view.laterId == nil { + let string = presentationData.strings.Chat_SavedMessagesTabInfoText + let formattedString = parseMarkdownIntoAttributedString( + string, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: .black), + bold: MarkdownAttributeSet(font: Font.regular(15.0), textColor: .white), + link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: .black), + linkAttribute: { url in + return ("URL", url) + } + ) + ) + var entities: [MessageTextEntity] = [] + formattedString.enumerateAttribute(.foregroundColor, in: NSRange(location: 0, length: formattedString.length), options: [], using: { value, range, _ in + if let value = value as? UIColor, value == .white { + entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Bold)) + } + }) + + let message = Message( + stableId: UInt32.max - 1001, + stableVersion: 0, + id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 123), + globallyUniqueId: nil, + groupingKey: nil, + groupInfo: nil, + threadId: nil, + timestamp: Int32.max - 1, + flags: [.Incoming], + tags: [], + globalTags: [], + localTags: [], + customTags: [], + forwardInfo: nil, + author: nil, + text: "", + attributes: [], + media: [TelegramMediaAction(action: .customText(text: formattedString.string, entities: entities, additionalAttributes: nil))], + peers: SimpleDictionary(), + associatedMessages: SimpleDictionary(), + associatedMessageIds: [], + associatedMedia: [:], + associatedThreadInfo: nil, + associatedStories: [:] + ) + entries.append(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil))) + } + } if reverse { return entries.reversed() diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 6db780617d8..e863c168ee4 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -11,6 +11,7 @@ import SwiftSignalKit import Display import AsyncDisplayKit import TelegramCore +import Postbox import TelegramPresentationData import TelegramUIPreferences import MediaResources @@ -159,6 +160,7 @@ struct ChatHistoryListViewTransition { var animateIn: Bool var reason: ChatHistoryViewTransitionReason var flashIndicators: Bool + var animateFromPreviousFilter: Bool } private func maxMessageIndexForEntries(_ view: ChatHistoryView, indexRange: (Int, Int)) -> (incoming: MessageIndex?, overall: MessageIndex?) { @@ -318,8 +320,10 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca } } -private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, transition: ChatHistoryViewTransition, wantTrButton: [(Bool, [String])]) -> ChatHistoryListViewTransition { - return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.insertEntries, wantTrButton: wantTrButton), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.updateEntries, wantTrButton: wantTrButton), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators) +// MARK: Nicegram, wantTrButton +private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, animateFromPreviousFilter: Bool, transition: ChatHistoryViewTransition, wantTrButton: [(Bool, [String])]) -> ChatHistoryListViewTransition { + // MARK: Nicegram, wantTrButton + return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.insertEntries, wantTrButton: wantTrButton), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.updateEntries, wantTrButton: wantTrButton), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: animateFromPreviousFilter) } private final class ChatHistoryTransactionOpaqueState { @@ -340,6 +344,7 @@ private func extractAssociatedData( currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, + savedMessageTags: SavedMessageTags?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, alwaysDisplayTranscribeButton: ChatMessageItemAssociatedData.DisplayTranscribeButton, @@ -351,7 +356,8 @@ private func extractAssociatedData( recommendedChannels: RecommendedChannels?, audioTranscriptionTrial: AudioTranscription.TrialState, chatThemes: [TelegramTheme], - deviceContactsNumbers: Set + deviceContactsNumbers: Set, + isInline: Bool ) -> ChatMessageItemAssociatedData { var automaticDownloadPeerId: EnginePeer.Id? var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel @@ -406,7 +412,7 @@ private func extractAssociatedData( automaticDownloadPeerId = message.peerId } - return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers) + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline) } private extension ChatHistoryLocationInput { @@ -460,7 +466,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto private let chatLocationContextHolder: Atomic private let source: ChatHistoryListSource private let subject: ChatControllerSubject? - private var tag: HistoryViewInputTag? + private(set) var tag: HistoryViewInputTag? private let controllerInteraction: ChatControllerInteraction private let selectedMessages: Signal?, NoError> private let messageTransitionNode: () -> ChatMessageTransitionNodeImpl? @@ -714,6 +720,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto private var allowDustEffect: Bool = true private var dustEffectLayer: DustEffectLayer? + var frozenMessageForScrollingReset: EngineMessage.Id? + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tag: HistoryViewInputTag?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, rotated: Bool = false, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) { // MARK: Nicegram self.wantTrButton = usetrButton() @@ -1157,11 +1165,41 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto let selectedMessages = self.selectedMessages let messageTransitionNode = self.messageTransitionNode let mode = self.mode + let rotated = self.rotated + + var resetScrollingMessageId: (index: MessageIndex, offset: CGFloat)? var resetScrolling = resetScrolling if resetScrolling { - self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: historyMessageCount), id: 0) + if let frozenMessageForScrollingReset = self.frozenMessageForScrollingReset { + self.forEachVisibleMessageItemNode { itemNode in + if resetScrollingMessageId != nil { + return + } + if let item = itemNode.item, item.message.id == frozenMessageForScrollingReset { + let distanceToNode = self.insets.top - itemNode.frame.minY + resetScrollingMessageId = (item.message.index, -distanceToNode) + } + } + } + + self.forEachVisibleMessageItemNode { itemNode in + if resetScrollingMessageId != nil { + return + } + if let item = itemNode.item { + let distanceToNode = self.insets.top - itemNode.frame.minY + resetScrollingMessageId = (item.message.index, -distanceToNode) + } + } + + if let resetScrollingMessageId { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .message(resetScrollingMessageId.index), quote: nil), anchorIndex: .message(resetScrollingMessageId.index), sourceIndex: .message(resetScrollingMessageId.index), scrollPosition: .top(resetScrollingMessageId.offset), animated: false, highlight: false), id: (self.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) + } else { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: historyMessageCount), id: (self.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) + } } + self.frozenMessageForScrollingReset = nil var appendMessagesFromTheSameGroup = false if case .pinnedMessages = subject { @@ -1354,6 +1392,12 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } let availableReactions: Signal = (context as! AccountContextImpl).availableReactions + let savedMessageTags: Signal + if chatLocation.peerId == self.context.account.peerId { + savedMessageTags = context.engine.stickers.savedMessageTagData() + } else { + savedMessageTags = .single(nil) + } let defaultReaction = combineLatest( self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), @@ -1457,6 +1501,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, + savedMessageTags, defaultReaction, accountPeer, audioTranscriptionSuggestion, @@ -1468,7 +1513,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto audioTranscriptionTrial, chatThemes, deviceContactsNumbers - ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers in + ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers in let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises func applyHole() { @@ -1546,6 +1591,60 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } initialData = combinedInitialData + + if resetScrolling, let previousViewValue = previousView.with({ $0 })?.0 { + let filteredEntries: [ChatHistoryEntry] = [] + let processedView = ChatHistoryView(originalView: MessageHistoryView(tag: nil, namespaces: .all, entries: [], holeEarlier: false, holeLater: false, isLoading: true), filteredEntries: filteredEntries, associatedData: previousViewValue.associatedData, lastHeaderId: 0, id: previousViewValue.id, locationInput: previousViewValue.locationInput, ignoreMessagesInTimestampRange: nil) + let previousValueAndVersion = previousView.swap((processedView, update.1, selectedMessages, allAdMessages.version)) + let previous = previousValueAndVersion?.0 + let previousSelectedMessages = previousValueAndVersion?.2 + + if let previousVersion = previousValueAndVersion?.1 { + assert(update.1 >= previousVersion) + } + + var reason: ChatHistoryViewTransitionReason + reason = ChatHistoryViewTransitionReason.InteractiveChanges + + let disableAnimations = true + let forceSynchronous = true + + let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: nil, scrollAnimationCurve: nil, initialData: initialData?.initialData, keyboardButtonsMessage: nil, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: false, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: false) + // MARK: Nicegram, wantTrButton + var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: previousViewValue.associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: 0, animateFromPreviousFilter: resetScrolling, transition: rawTransition, wantTrButton: self?.wantTrButton ?? [(false, [])]) + + if disableAnimations { + mappedTransition.options.remove(.AnimateInsertion) + mappedTransition.options.remove(.AnimateAlpha) + mappedTransition.options.remove(.AnimateTopItemPosition) + mappedTransition.options.remove(.RequestItemInsertionAnimations) + } + if forceSynchronous || resetScrolling { + mappedTransition.options.insert(.Synchronous) + } + if resetScrolling { + mappedTransition.options.insert(.AnimateAlpha) + mappedTransition.options.insert(.AnimateFullTransition) + } + + if resetScrolling { + resetScrolling = false + } + + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + if strongSelf.appliedPlayingMessageId?.0 != currentlyPlayingMessageIdAndType?.0 { + strongSelf.appliedPlayingMessageId = currentlyPlayingMessageIdAndType + } + if strongSelf.appliedScrollToMessageId != scrollToMessageId { + strongSelf.appliedScrollToMessageId = scrollToMessageId + } + strongSelf.enqueueHistoryViewTransition(mappedTransition) + } + } + Queue.mainQueue().async { if let strongSelf = self { if !strongSelf.didSetInitialData { @@ -1627,7 +1726,12 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto translateToLanguage = languageCode } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated) + + var includeEmbeddedSavedChatInfo = false + if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId, !rotated { + includeEmbeddedSavedChatInfo = true + } let filteredEntries = chatHistoryEntriesForView( location: chatLocation, @@ -1636,6 +1740,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto includeEmptyEntry: mode == .bubbles && tag == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tag != nil, + includeEmbeddedSavedChatInfo: includeEmbeddedSavedChatInfo, reverse: reverse, groupMessages: mode == .bubbles, reverseGroupedMessages: reverseGroups, @@ -1772,10 +1877,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } } - if resetScrolling { - resetScrolling = false - } - if let strongSelf = self, updatedScrollPosition == nil, case .InteractiveChanges = reason, case let .known(offset) = strongSelf.visibleContentOffset(), abs(offset) <= 0.9, let previous = previous { var fillsScreen = true switch strongSelf.visibleBottomContentOffset() { @@ -1855,7 +1956,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: updateAllOnEachVersion || forceUpdateAll) - var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, transition: rawTransition, wantTrButton: self?.wantTrButton ?? [(false, [])]) + // MARK: Nicegram, wantTrButton + var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, animateFromPreviousFilter: resetScrolling, transition: rawTransition, wantTrButton: self?.wantTrButton ?? [(false, [])]) if disableAnimations { mappedTransition.options.remove(.AnimateInsertion) @@ -1863,9 +1965,17 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto mappedTransition.options.remove(.AnimateTopItemPosition) mappedTransition.options.remove(.RequestItemInsertionAnimations) } - if forceSynchronous { + if forceSynchronous || resetScrolling { mappedTransition.options.insert(.Synchronous) } + if resetScrolling { + mappedTransition.options.insert(.AnimateAlpha) + mappedTransition.options.insert(.AnimateFullTransition) + } + + if resetScrolling { + resetScrolling = false + } Queue.mainQueue().async { guard let strongSelf = self else { @@ -3181,6 +3291,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.currentAppliedDeleteAnimationCorrelationIds = appliedDeleteAnimationCorrelationIds let animated = transition.options.contains(.AnimateInsertion) + + var previousCloneView: UIView? + if transition.animateFromPreviousFilter, !"".isEmpty { + previousCloneView = self.view.snapshotView(afterScreenUpdates: false) + } let completion: (Bool, ListViewDisplayedItemRange) -> Void = { [weak self] wasTransformed, visibleRange in if let strongSelf = self { @@ -3524,6 +3639,17 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto strongSelf.hasActiveTransition = false + if let previousCloneView { + previousCloneView.transform = strongSelf.view.transform + previousCloneView.center = strongSelf.view.center + previousCloneView.bounds = strongSelf.view.bounds + strongSelf.view.superview?.insertSubview(previousCloneView, belowSubview: strongSelf.view) + strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + previousCloneView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousCloneView] _ in + previousCloneView?.removeFromSuperview() + }) + } + strongSelf.dequeueHistoryViewTransitions() } } @@ -3709,6 +3835,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto let updateSizeAndInsets = updateSizeAndInsets.with(insets: insets) // + /*if updateSizeAndInsets.insets.top == 83.0 { + if !transition.isAnimated { + assert(true) + } + }*/ var scrollToItem: ListViewScrollToItem? var postScrollToItem: ListViewScrollToItem? if scrollToTop, case .known = self.visibleContentOffset() { diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift index 7dc96eea3ee..9648d94310e 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift @@ -17,10 +17,11 @@ enum ChatHistoryNavigationButtonType { class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { let containerNode: ContextExtractedContentContainingNode - private let buttonNode: HighlightTrackingButtonNode + let buttonNode: HighlightTrackingButtonNode private let backgroundNode: NavigationBackgroundNode private var backgroundContent: WallpaperBubbleBackgroundNode? - private let imageNode: ASImageNode + let backgroundImageNode: ASImageNode + let imageNode: ASImageNode private let badgeBackgroundNode: ASImageNode private let badgeTextNode: ImmediateAnimatedCountLabelNode @@ -56,6 +57,11 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.backgroundNode = NavigationBackgroundNode(color: theme.chat.inputPanel.panelBackgroundColor) + self.backgroundImageNode = ASImageNode() + self.backgroundImageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBackground(theme) + self.backgroundImageNode.isLayerBacked = true + + self.backgroundImageNode.displayWithoutProcessing = true self.imageNode = ASImageNode() self.imageNode.displayWithoutProcessing = true switch type { @@ -99,7 +105,9 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: size.width / 2.0, transition: .immediate) + self.buttonNode.addSubnode(self.backgroundImageNode) self.buttonNode.addSubnode(self.imageNode) + self.backgroundImageNode.frame = CGRect(origin: CGPoint(), size: size) self.imageNode.frame = CGRect(origin: CGPoint(), size: size) self.buttonNode.addSubnode(self.badgeBackgroundNode) @@ -123,6 +131,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { case .reactions: self.imageNode.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme) } + self.backgroundImageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBackground(theme) self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme) var segments: [AnimatedCountLabelNode.Segment] = [] diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift index 085418897a0..0575f94bb21 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift @@ -2,6 +2,7 @@ import NGData // import Foundation +import Foundation import UIKit import Display import AsyncDisplayKit @@ -9,13 +10,32 @@ import TelegramPresentationData import WallpaperBackgroundNode final class ChatHistoryNavigationButtons: ASDisplayNode { + struct ButtonState: Equatable { + var isEnabled: Bool + + init(isEnabled: Bool) { + self.isEnabled = isEnabled + } + } + + struct DirectionState: Equatable { + var up: ButtonState? + var down: ButtonState? + + init(up: ButtonState?, down: ButtonState?) { + self.up = up + self.down = down + } + } + private var theme: PresentationTheme private var dateTimeFormat: PresentationDateTimeFormat private let isChatRotated: Bool let reactionsButton: ChatHistoryNavigationButtonNode let mentionsButton: ChatHistoryNavigationButtonNode - private let downButton: ChatHistoryNavigationButtonNode + let downButton: ChatHistoryNavigationButtonNode + let upButton: ChatHistoryNavigationButtonNode var downPressed: (() -> Void)? { didSet { @@ -23,12 +43,18 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } } + var upPressed: (() -> Void)? { + didSet { + self.upButton.tapped = self.upPressed + } + } + var reactionsPressed: (() -> Void)? var mentionsPressed: (() -> Void)? - var displayDownButton: Bool = false { + var directionButtonState: DirectionState = DirectionState(up: nil, down: nil) { didSet { - if oldValue != self.displayDownButton { + if oldValue != self.directionButtonState { let _ = self.updateLayout(transition: .animated(duration: 0.3, curve: .spring)) } } @@ -89,11 +115,16 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { self.downButton.alpha = 0.0 self.downButton.isHidden = true + self.upButton = ChatHistoryNavigationButtonNode(theme: theme, backgroundNode: backgroundNode, type: isChatRotated ? .up : .down) + self.upButton.alpha = 0.0 + self.upButton.isHidden = true + super.init() self.addSubnode(self.reactionsButton) self.addSubnode(self.mentionsButton) self.addSubnode(self.downButton) + self.addSubnode(self.upButton) self.reactionsButton.tapped = { [weak self] in self?.reactionsPressed?() @@ -104,6 +135,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } self.downButton.isGestureEnabled = false + self.upButton.isGestureEnabled = false } override func didLoad() { @@ -117,6 +149,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { self.reactionsButton.updateTheme(theme: theme, backgroundNode: backgroundNode) self.mentionsButton.updateTheme(theme: theme, backgroundNode: backgroundNode) self.downButton.updateTheme(theme: theme, backgroundNode: backgroundNode) + self.upButton.updateTheme(theme: theme, backgroundNode: backgroundNode) } private var absoluteRect: (CGRect, CGSize)? @@ -133,6 +166,11 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { mentionsFrame.origin.y += rect.minY self.mentionsButton.update(rect: mentionsFrame, within: containerSize, transition: transition) + var upFrame = self.upButton.frame + upFrame.origin.x += rect.minX + upFrame.origin.y += rect.minY + self.upButton.update(rect: upFrame, within: containerSize, transition: transition) + var downFrame = self.downButton.frame downFrame.origin.x += rect.minX downFrame.origin.y += rect.minY @@ -142,11 +180,16 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize { let buttonSize = CGSize(width: 38.0, height: 38.0) let completeSize = CGSize(width: buttonSize.width, height: buttonSize.height * 2.0 + 12.0) + var upOffset: CGFloat = 0.0 var mentionsOffset: CGFloat = 0.0 var reactionsOffset: CGFloat = 0.0 - if self.displayDownButton { + if let down = self.directionButtonState.down { + self.downButton.imageNode.alpha = down.isEnabled ? 1.0 : 0.5 + self.downButton.buttonNode.isEnabled = down.isEnabled + mentionsOffset += buttonSize.height + 12.0 + upOffset += buttonSize.height + 12.0 self.downButton.isHidden = false transition.updateAlpha(node: self.downButton, alpha: 1.0) @@ -161,6 +204,25 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { transition.updateTransformScale(node: self.downButton, scale: 0.2) } + if let up = self.directionButtonState.up { + self.upButton.imageNode.alpha = up.isEnabled ? 1.0 : 0.5 + self.upButton.buttonNode.isEnabled = up.isEnabled + + mentionsOffset += buttonSize.height + 12.0 + + self.upButton.isHidden = false + transition.updateAlpha(node: self.upButton, alpha: 1.0) + transition.updateTransformScale(node: self.upButton, scale: 1.0) + } else { + transition.updateAlpha(node: self.upButton, alpha: 0.0, completion: { [weak self] completed in + guard let strongSelf = self, completed else { + return + } + strongSelf.upButton.isHidden = true + }) + transition.updateTransformScale(node: self.upButton, scale: 0.2) + } + if self.mentionCount != 0 { reactionsOffset += buttonSize.height + 12.0 @@ -194,10 +256,12 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { if self.isChatRotated { transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center) + transition.updatePosition(node: self.upButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - upOffset), size: buttonSize).center) transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center) transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center) } else { transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: buttonSize).center) + transition.updatePosition(node: self.upButton, position: CGRect(origin: CGPoint(x: 0.0, y: upOffset), size: buttonSize).center) transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset), size: buttonSize).center) transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset + reactionsOffset), size: buttonSize).center) } diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index 4931b92c215..c4b2a8b8b2f 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -82,8 +82,19 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess var preloaded = false var fadeIn = false let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> - if let tag { - signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, index: .upperBound, anchorIndex: .upperBound, count: count, ignoreRelatedChats: ignoreRelatedChats, fixedCombinedReadStates: nil, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, orderStatistics: orderStatistics) + + var requestAroundId = false + var preFixedReadState: MessageHistoryViewReadState? + if tag != nil { + requestAroundId = true + } + if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId { + requestAroundId = true + preFixedReadState = .peer([:]) + } + + if requestAroundId { + signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, index: .upperBound, anchorIndex: .upperBound, count: count, ignoreRelatedChats: ignoreRelatedChats, fixedCombinedReadStates: preFixedReadState, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, orderStatistics: orderStatistics) } else { signal = account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, orderStatistics: orderStatistics, additionalData: additionalData) } @@ -93,7 +104,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData) - if preloaded { + if preloaded { return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil, flashIndicators: false, originalScrollPosition: nil, initialData: combinedInitialData, id: location.id) } else { if view.isLoading { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 014d3d57d7c..f039276b60b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -309,7 +309,9 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS break } if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation, replyThreadMessage.peerId == accountPeerId { - return false + if replyThreadMessage.threadId != accountPeerId.toInt64() { + return false + } } if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { @@ -787,11 +789,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } + let isScheduled = chatPresentationInterfaceState.subject == .scheduledMessages + let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest( loadLimits, loadStickerSaveStatusSignal, loadResourceStatusSignal, - context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: Set(messages.map { $0.id })), + context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: Set(messages.map { $0.id }), keepUpdated: false), context.account.pendingUpdateMessageManager.updatingMessageMedia |> take(1), infoSummaryData, @@ -824,6 +828,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState loggingSettings = LoggingSettings.defaultSettings } + var messageActions = messageActions + if isEmbeddedMode { + messageActions = ChatAvailableMessageActions( + options: messageActions.options.intersection([.deleteLocally, .deleteGlobally, .forward]), + banAuthor: nil, + disableDelete: true, + isCopyProtected: messageActions.isCopyProtected, + setTag: false, + editTags: Set() + ) + } else if isScheduled { + messageActions.setTag = false + messageActions.editTags = Set() + } + return (MessageContextMenuData( starStatus: stickerSaveStatus, canReply: canReply && !isEmbeddedMode, @@ -831,12 +850,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState canEdit: canEdit && !isEmbeddedMode, canSelect: canSelect && !isEmbeddedMode, resourceStatus: resourceStatus, - messageActions: isEmbeddedMode ? ChatAvailableMessageActions( - options: [], - banAuthor: nil, - disableDelete: true, - isCopyProtected: messageActions.isCopyProtected - ) : messageActions + messageActions: messageActions ), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer) } @@ -1733,7 +1747,11 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState isSending = true title = chatPresentationInterfaceState.strings.Conversation_ContextMenuCancelSending } else { - title = chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete + if case .peer(context.account.peerId) = chatPresentationInterfaceState.chatLocation, message.effectivelyIncoming(context.account.peerId) { + title = chatPresentationInterfaceState.strings.Chat_MessageContextMenu_Remove + } else { + title = chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete + } } if let autoremoveDeadline = autoremoveDeadline, !isEditing, !isSending { @@ -2257,13 +2275,22 @@ private func canPerformDeleteActions(limits: LimitsConfiguration, accountPeerId: return false } -func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: PeerId, messageIds: Set, messages: [MessageId: Message] = [:], peers: [PeerId: Peer] = [:]) -> Signal { - return engine.data.get( +func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: PeerId, messageIds: Set, messages: [MessageId: Message] = [:], peers: [PeerId: Peer] = [:], keepUpdated: Bool) -> Signal { + return engine.data.subscribe( TelegramEngine.EngineData.Item.Configuration.Limits(), EngineDataMap(Set(messageIds.map(\.peerId)).map(TelegramEngine.EngineData.Item.Peer.Peer.init)), - EngineDataMap(Set(messageIds).map(TelegramEngine.EngineData.Item.Messages.Message.init)) + EngineDataMap(Set(messageIds).map(TelegramEngine.EngineData.Item.Messages.Message.init)), + TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId) ) - |> map { limitsConfiguration, peerMap, messageMap -> ChatAvailableMessageActions in + |> take(keepUpdated ? Int.max : 1) + |> map { limitsConfiguration, peerMap, messageMap, accountPeer -> ChatAvailableMessageActions in + let isPremium: Bool + if let accountPeer { + isPremium = accountPeer.isPremium + } else { + isPremium = false + } + var optionsMap: [MessageId: ChatAvailableMessageActionOptions] = [:] var banPeer: Peer? var hadPersonalIncoming = false @@ -2272,6 +2299,9 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer var isCopyProtected = false var isShareProtected = false + var setTag = false + var commonTags: Set? + func getPeer(_ peerId: PeerId) -> Peer? { if let maybePeer = peerMap[peerId], let peer = maybePeer { return peer._asPeer() @@ -2299,6 +2329,25 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer optionsMap[id] = [] } if let message = getMessage(id) { + if message.areReactionsTags(accountPeerId: accountPeerId) { + setTag = true + + var messageReactions = Set() + if let reactionsAttribute = mergedMessageReactions(attributes: message.attributes, isTags: message.areReactionsTags(accountPeerId: accountPeerId)) { + for reaction in reactionsAttribute.reactions { + messageReactions.insert(reaction.value) + } + } + if let commonTagsValue = commonTags { + if commonTagsValue == messageReactions { + } else { + commonTags?.removeAll() + } + } else { + commonTags = messageReactions + } + } + if message.isCopyProtected() || message.containsSecretMedia { isCopyProtected = true } @@ -2510,9 +2559,15 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer if hadPersonalIncoming && optionsMap.values.contains(where: { $0.contains(.deleteGlobally) }) && !reducedOptions.contains(.deleteGlobally) { reducedOptions.insert(.unsendPersonal) } - return ChatAvailableMessageActions(options: reducedOptions, banAuthor: banPeer, disableDelete: disableDelete, isCopyProtected: isCopyProtected) + + if !isPremium { + setTag = false + commonTags = nil + } + + return ChatAvailableMessageActions(options: reducedOptions, banAuthor: banPeer, disableDelete: disableDelete, isCopyProtected: isCopyProtected, setTag: setTag, editTags: commonTags ?? Set()) } else { - return ChatAvailableMessageActions(options: [], banAuthor: nil, disableDelete: false, isCopyProtected: isCopyProtected) + return ChatAvailableMessageActions(options: [], banAuthor: nil, disableDelete: false, isCopyProtected: isCopyProtected, setTag: false, editTags: Set()) } } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index b4553cf9f5f..cc47f104ea5 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -12,10 +12,6 @@ import ChatChannelSubscriberInputPanelNode import ChatMessageSelectionInputPanelNode func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) { - if case .standard(.embedded) = chatPresentationInterfaceState.mode { - return (nil, nil) - } - if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil { if isAllowedChat(peer: renderedPeer.peer, contentSettings: context.currentContentSettings.with { $0 }) { } else { @@ -51,29 +47,21 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState } } - if chatPresentationInterfaceState.historyFilter != nil { - if let currentPanel = (currentPanel as? ChatTagSearchInputPanelNode) ?? (currentSecondaryPanel as? ChatTagSearchInputPanelNode) { - currentPanel.interfaceInteraction = interfaceInteraction - return (currentPanel, selectionPanel) - } else { - let panel = ChatTagSearchInputPanelNode(theme: chatPresentationInterfaceState.theme) - panel.context = context - panel.interfaceInteraction = interfaceInteraction - return (panel, selectionPanel) - } + if let currentPanel = (currentPanel as? ChatTagSearchInputPanelNode) ?? (currentSecondaryPanel as? ChatTagSearchInputPanelNode) { + currentPanel.interfaceInteraction = interfaceInteraction + return (currentPanel, selectionPanel) } else { - if let currentPanel = (currentPanel as? ChatSearchInputPanelNode) ?? (currentSecondaryPanel as? ChatSearchInputPanelNode) { - currentPanel.interfaceInteraction = interfaceInteraction - return (currentPanel, selectionPanel) - } else { - let panel = ChatSearchInputPanelNode(theme: chatPresentationInterfaceState.theme) - panel.context = context - panel.interfaceInteraction = interfaceInteraction - return (panel, selectionPanel) - } + let panel = ChatTagSearchInputPanelNode(theme: chatPresentationInterfaceState.theme) + panel.context = context + panel.interfaceInteraction = interfaceInteraction + return (panel, selectionPanel) } } + if case .standard(.embedded) = chatPresentationInterfaceState.mode { + return (nil, nil) + } + if let selectionState = chatPresentationInterfaceState.interfaceState.selectionState { if let _ = chatPresentationInterfaceState.reportReason { if let currentPanel = (currentPanel as? ChatMessageReportInputPanelNode) ?? (currentSecondaryPanel as? ChatMessageReportInputPanelNode) { @@ -165,13 +153,16 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState return (panel, nil) } } else { - if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { - return (currentPanel, nil) + if message.threadId == context.account.peerId.toInt64() { } else { - let panel = ChatChannelSubscriberInputPanelNode() - panel.interfaceInteraction = interfaceInteraction - panel.context = context - return (panel, nil) + if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { + return (currentPanel, nil) + } else { + let panel = ChatChannelSubscriberInputPanelNode() + panel.interfaceInteraction = interfaceInteraction + panel.context = context + return (panel, nil) + } } } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 8cf582a26de..65268616861 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -72,7 +72,15 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present } if case let .replyThread(message) = presentationInterfaceState.chatLocation, message.peerId == context.account.peerId { - return chatInfoNavigationButton + let isTags = presentationInterfaceState.hasSearchTags + + if case .search(isTags) = currentButton?.action { + return currentButton + } else { + let buttonItem = UIBarButtonItem(image: isTags ? PresentationResourcesRootController.navigationCompactTagsSearchIcon(presentationInterfaceState.theme) : PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) + buttonItem.accessibilityLabel = strings.Conversation_Search + return ChatNavigationButton(action: .search(hasTags: isTags), buttonItem: buttonItem) + } } if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum), let moreInfoNavigationButton = moreInfoNavigationButton { @@ -108,7 +116,7 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present } else { let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) buttonItem.accessibilityLabel = strings.Conversation_Search - return ChatNavigationButton(action: .search, buttonItem: buttonItem) + return ChatNavigationButton(action: .search(hasTags: false), buttonItem: buttonItem) } } else { if case .spacer = currentButton?.action { @@ -126,7 +134,7 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present } else { let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) buttonItem.accessibilityLabel = strings.Conversation_Search - return ChatNavigationButton(action: .search, buttonItem: buttonItem) + return ChatNavigationButton(action: .search(hasTags: false), buttonItem: buttonItem) } } else { if case .spacer = currentButton?.action { @@ -144,25 +152,29 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present if case .standard(.previewing) = presentationInterfaceState.mode { return chatInfoNavigationButton - } else if let peer = presentationInterfaceState.renderedPeer?.peer { - if presentationInterfaceState.accountPeerId == peer.id { + } else if let peerId = presentationInterfaceState.chatLocation.peerId { + if presentationInterfaceState.accountPeerId == peerId { + var displaySearchButton = false + + if case .replyThread = presentationInterfaceState.chatLocation { + displaySearchButton = true + } + if case .scheduledMessages = presentationInterfaceState.subject { return chatInfoNavigationButton } else { - if presentationInterfaceState.hasPlentyOfMessages { - if case .search = currentButton?.action { - return currentButton - } else { - let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) - buttonItem.accessibilityLabel = strings.Conversation_Search - return ChatNavigationButton(action: .search, buttonItem: buttonItem) - } + displaySearchButton = true + } + + if displaySearchButton { + let isTags = presentationInterfaceState.hasSearchTags + + if case .search(isTags) = currentButton?.action { + return currentButton } else { - if case .spacer = currentButton?.action { - return currentButton - } else { - return ChatNavigationButton(action: .spacer, buttonItem: UIBarButtonItem(title: "", style: .plain, target: target, action: selector)) - } + let buttonItem = UIBarButtonItem(image: isTags ? PresentationResourcesRootController.navigationCompactTagsSearchIcon(presentationInterfaceState.theme) : PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) + buttonItem.accessibilityLabel = strings.Conversation_Search + return ChatNavigationButton(action: .search(hasTags: isTags), buttonItem: buttonItem) } } } @@ -170,3 +182,16 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present return chatInfoNavigationButton } + +func secondaryRightNavigationButtonForChatInterfaceState(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?, chatInfoNavigationButton: ChatNavigationButton?, moreInfoNavigationButton: ChatNavigationButton?) -> ChatNavigationButton? { + if presentationInterfaceState.interfaceState.selectionState != nil { + return nil + } + if case .standard(.default) = presentationInterfaceState.mode { + if case .peer(context.account.peerId) = presentationInterfaceState.chatLocation, presentationInterfaceState.subject != .scheduledMessages { + return moreInfoNavigationButton + } + } + + return nil +} diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 8fd99c92d0e..05f0437ce29 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -6,7 +6,11 @@ import NGWebUtils import ChatPresentationInterfaceState import ChatControllerInteraction -func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatTitleAccessoryPanelNode? { +func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTitleAccessoryPanelNode? { + if !force, case .standard(.embedded) = chatPresentationInterfaceState.mode { + return nil + } + if case .overlay = chatPresentationInterfaceState.mode { return nil } @@ -20,12 +24,28 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat return nil } } - if let search = chatPresentationInterfaceState.search, chatPresentationInterfaceState.hasSearchTags { - if chatPresentationInterfaceState.chatLocation.peerId == context.account.peerId, case .everything = search.domain { + if let search = chatPresentationInterfaceState.search { + var matches = false + if chatPresentationInterfaceState.chatLocation.peerId == context.account.peerId { + if chatPresentationInterfaceState.hasSearchTags || !chatPresentationInterfaceState.isPremium { + if case .everything = search.domain { + matches = true + } else if case .tag = search.domain, search.query.isEmpty { + matches = true + } + } + } + if case .standard(.embedded) = chatPresentationInterfaceState.mode { + if !chatPresentationInterfaceState.isPremium { + matches = false + } + } + + if matches { if let currentPanel = currentPanel as? ChatSearchTitleAccessoryPanelNode { return currentPanel } else { - let panel = ChatSearchTitleAccessoryPanelNode(context: context) + let panel = ChatSearchTitleAccessoryPanelNode(context: context, chatLocation: chatPresentationInterfaceState.chatLocation) panel.interfaceInteraction = interfaceInteraction return panel } diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 63002ef7ca7..9b4547a6119 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -856,13 +856,13 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran self.standaloneReactionAnimation = standaloneReactionAnimation } - func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?) { + func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?, isRotated: Bool) { guard let currentItemNode = self.itemNode else { return } if itemNode == nil || itemNode === currentItemNode { if let contextController = self.contextController { - contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) + contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: offset), transition: transition) } if let standaloneReactionAnimation = self.standaloneReactionAnimation { standaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) @@ -1084,7 +1084,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } } - func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?) { + func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?, isRotated: Bool) { for animatingItemNode in self.animatingItemNodes { animatingItemNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) } @@ -1094,7 +1094,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } } for messageReactionContext in self.messageReactionContexts { - messageReactionContext.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) + messageReactionContext.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode, isRotated: isRotated) } } diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift index c620d0e2c36..2004cc0d1b2 100644 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift @@ -65,8 +65,8 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { super.init() - self.addSubnode(self.upButton) - self.addSubnode(self.downButton) + //self.addSubnode(self.upButton) + //self.addSubnode(self.downButton) self.addSubnode(self.calendarButton) self.addSubnode(self.membersButton) self.addSubnode(self.resultsButton) diff --git a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift index 35bbdc093c9..8ad90ba599f 100644 --- a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift @@ -36,7 +36,11 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { switch chatLocation { case .peer, .replyThread, .feed: if chatLocation.peerId == context.account.peerId, presentationInterfaceState.hasSearchTags { - placeholderText = strings.Chat_SearchTagsPlaceholder + if case .standard(.embedded(false)) = presentationInterfaceState.mode { + placeholderText = strings.Common_Search + } else { + placeholderText = strings.Chat_SearchTagsPlaceholder + } } else { placeholderText = strings.Conversation_SearchPlaceholder } @@ -105,7 +109,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), strings: presentationInterfaceState.strings) switch search.domain { - case .everything: + case .everything, .tag: self.searchBar.tokens = [] self.searchBar.prefixString = nil let placeholderText: String @@ -114,7 +118,11 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { if presentationInterfaceState.historyFilter != nil { placeholderText = self.strings.Common_Search } else if self.chatLocation.peerId == self.context.account.peerId, presentationInterfaceState.hasSearchTags { - placeholderText = self.strings.Chat_SearchTagsPlaceholder + if case .standard(.embedded(false)) = presentationInterfaceState.mode { + placeholderText = strings.Common_Search + } else { + placeholderText = self.strings.Chat_SearchTagsPlaceholder + } } else { placeholderText = self.strings.Conversation_SearchPlaceholder } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 69b0505429f..b2f4d0f1bbb 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -79,6 +79,11 @@ private enum ChatListSearchEntry: Comparable, Identifiable { public func item(context: AccountContext, interaction: ChatListNodeInteraction, location: ChatListControllerLocation) -> ListViewItem { switch self { case let .message(message, peer, readState, presentationData): + var displayAsMessage = true + if case .savedMessagesChats = location { + displayAsMessage = false + } + return ChatListItem( presentationData: presentationData, context: context, @@ -98,13 +103,14 @@ private enum ChatListSearchEntry: Comparable, Identifiable { inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, - displayAsMessage: true, + displayAsMessage: displayAsMessage, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, diff --git a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift index 3838cb869b3..9f15bc671ea 100644 --- a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift @@ -10,10 +10,23 @@ import MultilineTextComponent import PlainButtonComponent import UIKitRuntimeUtils import TelegramCore +import Postbox import EmojiStatusComponent import SwiftSignalKit +import ContextUI +import PromptUI +import BundleIconComponent +import SavedTagNameAlertController -final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UIScrollViewDelegate { +private let backgroundTagImage: UIImage? = { + if let image = UIImage(bundleImageName: "Chat/Title Panels/SearchTagTab") { + return image.stretchableImage(withLeftCapWidth: 8, topCapHeight: 0).withRenderingMode(.alwaysTemplate) + } else { + return nil + } +}() + +final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, ChatControllerCustomNavigationPanelNode, UIScrollViewDelegate { private struct Params: Equatable { var width: CGFloat var leftInset: CGFloat @@ -47,39 +60,206 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc private final class Item { let reaction: MessageReaction.Reaction let count: Int + let title: String? let file: TelegramMediaFile - init(reaction: MessageReaction.Reaction, count: Int, file: TelegramMediaFile) { + init(reaction: MessageReaction.Reaction, count: Int, title: String?, file: TelegramMediaFile) { self.reaction = reaction self.count = count + self.title = title self.file = file } } - private final class ItemView: HighlightTrackingButton { + private final class PromoView: UIView { + private let containerButton: HighlightTrackingButton + + private let background: UIImageView + private let titleIcon = ComponentView() + private let title = ComponentView() + private let text = ComponentView() + private let arrowIcon = ComponentView() + + let action: () -> Void + + init(action: @escaping () -> Void) { + self.action = action + + self.containerButton = HighlightTrackingButton() + + self.background = UIImageView() + self.background.image = backgroundTagImage + + super.init(frame: CGRect()) + + self.containerButton.layer.allowsGroupOpacity = true + + self.containerButton.addSubview(self.background) + + self.addSubview(self.containerButton) + + self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.containerButton.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.containerButton.alpha = 0.7 + } else { + Transition.easeInOut(duration: 0.25).setAlpha(view: self.containerButton, alpha: 1.0) + } + } + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + @objc private func pressed() { + self.action() + } + + func update(theme: PresentationTheme, strings: PresentationStrings, height: CGFloat, isUnlock: Bool, transition: Transition) -> CGSize { + let titleIconSpacing: CGFloat = 0.0 + + let titleIconSize = self.titleIcon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent( + name: "Chat/Stickers/Lock", + tintColor: theme.rootController.navigationBar.accentTextColor, + maxSize: CGSize(width: 14.0, height: 14.0) + )), + environment: {}, + containerSize: CGSize(width: 14.0, height: 14.0) + ) + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: isUnlock ? strings.Chat_TagsHeaderPanel_Unlock : strings.Chat_TagsHeaderPanel_AddTags, font: Font.medium(14.0), textColor: theme.rootController.navigationBar.accentTextColor)) + )), + environment: {}, + containerSize: CGSize(width: 200.0, height: 100.0) + ) + + let size = CGSize(width: titleIconSize.width + titleIconSpacing + titleSize.width - 1.0, height: height) + + let titleIconFrame = CGRect(origin: CGPoint(x: -1.0, y: UIScreenPixel + floor((size.height - titleIconSize.height) * 0.5)), size: titleIconSize) + if let titleIconView = self.titleIcon.view { + if titleIconView.superview == nil { + titleIconView.isUserInteractionEnabled = false + self.containerButton.addSubview(titleIconView) + } + titleIconView.frame = titleIconFrame + } + + let titleFrame = CGRect(origin: CGPoint(x: titleIconSize.width + titleIconSpacing, y: floor((size.height - titleSize.height) * 0.5)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + titleView.isUserInteractionEnabled = false + self.containerButton.addSubview(titleView) + } + titleView.frame = titleFrame + } + + self.background.tintColor = theme.rootController.navigationBar.accentTextColor.withMultipliedAlpha(0.1) + + if let image = self.background.image { + let backgroundFrame = CGRect(origin: CGPoint(x: -6.0, y: floorToScreenPixels((size.height - image.size.height) * 0.5)), size: CGSize(width: size.width + 6.0 + 9.0, height: image.size.height)) + transition.setFrame(view: self.background, frame: backgroundFrame) + } + + var totalSize = size + + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: strings.Chat_TagsHeaderPanel_AddTagsSuffix, font: Font.regular(14.0), textColor: theme.rootController.navigationBar.secondaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: 200.0, height: 100.0) + ) + let arrowSize = self.arrowIcon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent( + name: "Item List/DisclosureArrow", + tintColor: theme.rootController.navigationBar.secondaryTextColor.withMultipliedAlpha(0.6) + )), + environment: {}, + containerSize: CGSize(width: 200.0, height: 100.0) + ) + let textSpacing: CGFloat = 13.0 + let arrowSpacing: CGFloat = -5.0 + + totalSize.width += textSpacing + + let textFrame = CGRect(origin: CGPoint(x: totalSize.width, y: floor((size.height - textSize.height) * 0.5)), size: textSize) + if let textView = self.text.view { + if textView.superview == nil { + textView.isUserInteractionEnabled = false + self.containerButton.addSubview(textView) + } + textView.frame = textFrame + transition.setAlpha(view: textView, alpha: isUnlock ? 0.0 : 1.0) + } + totalSize.width += textSize.width + totalSize.width += arrowSpacing + + let arrowFrame = CGRect(origin: CGPoint(x: totalSize.width, y: 1.0 + floor((size.height - arrowSize.height) * 0.5)), size: arrowSize) + if let arrowIconView = self.arrowIcon.view { + if arrowIconView.superview == nil { + arrowIconView.isUserInteractionEnabled = false + self.containerButton.addSubview(arrowIconView) + } + arrowIconView.frame = arrowFrame + transition.setAlpha(view: arrowIconView, alpha: isUnlock ? 0.0 : 1.0) + } + totalSize.width += arrowSize.width + + transition.setFrame(view: self.containerButton, frame: CGRect(origin: CGPoint(), size: totalSize)) + + return isUnlock ? size : totalSize + } + } + + private final class ItemView: UIView { private let context: AccountContext private let action: () -> Void + private let extractedContainerNode: ContextExtractedContentContainingNode + private let containerNode: ContextControllerSourceNode + + private let containerButton: HighlightTrackingButton + private let background: UIImageView private let icon = ComponentView() + private let title = ComponentView() private let counter = ComponentView() - init(context: AccountContext, action: @escaping (() -> Void)) { - self.background = UIImageView() - if let image = UIImage(bundleImageName: "Chat/Title Panels/SearchTagTab") { - self.background.image = image.stretchableImage(withLeftCapWidth: 8, topCapHeight: 0).withRenderingMode(.alwaysTemplate) - } - + init(context: AccountContext, action: @escaping (() -> Void), contextGesture: @escaping (ContextGesture, ContextExtractedContentContainingNode) -> Void) { self.context = context self.action = action + self.extractedContainerNode = ContextExtractedContentContainingNode() + self.containerNode = ContextControllerSourceNode() + + self.containerButton = HighlightTrackingButton() + + self.background = UIImageView() + self.background.image = backgroundTagImage + super.init(frame: CGRect()) - self.addSubview(self.background) + self.extractedContainerNode.contentNode.view.addSubview(self.containerButton) + + self.containerNode.addSubnode(self.extractedContainerNode) + self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode + self.addSubview(self.containerNode.view) - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.containerButton.addSubview(self.background) - self.highligthedChanged = { [weak self] highlighted in + self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.containerButton.highligthedChanged = { [weak self] highlighted in if let self, self.bounds.width > 0.0 { let topScale: CGFloat = (self.bounds.width - 1.0) / self.bounds.width let maxScale: CGFloat = (self.bounds.width + 1.0) / self.bounds.width @@ -103,6 +283,13 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc } } } + + self.containerNode.activated = { [weak self] gesture, _ in + guard let self else { + return + } + contextGesture(gesture, self.extractedContainerNode) + } } required init?(coder: NSCoder) { @@ -121,10 +308,12 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc return super.hitTest(mappedPoint, with: event) } - func update(item: Item, isSelected: Bool, theme: PresentationTheme, height: CGFloat, transition: Transition) -> CGSize { - let spacing: CGFloat = 4.0 + func update(item: Item, isSelected: Bool, isLocked: Bool, theme: PresentationTheme, height: CGFloat, transition: Transition) -> CGSize { + let spacing: CGFloat = 3.0 - let reactionSize = CGSize(width: 16.0, height: 16.0) + let contentsAlpha: CGFloat = isLocked ? 0.6 : 1.0 + + let reactionSize = CGSize(width: 20.0, height: 20.0) var reactionDisplaySize = reactionSize if case .builtin = item.reaction { reactionDisplaySize = CGSize(width: reactionDisplaySize.width * 2.0, height: reactionDisplaySize.height * 2.0) @@ -152,42 +341,84 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc containerSize: reactionDisplaySize ) + let titleText: String = item.title ?? "" + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: titleText, font: Font.regular(11.0), textColor: isSelected ? theme.list.itemCheckColors.foregroundColor : theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.6))) + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + + let counterText: String = "\(item.count)" let counterSize = self.counter.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "\(item.count)", font: Font.regular(14.0), textColor: isSelected ? theme.list.itemCheckColors.foregroundColor : theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.6))) + text: .plain(NSAttributedString(string: counterText, font: Font.regular(11.0), textColor: isSelected ? theme.list.itemCheckColors.foregroundColor : theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.6))) )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) ) - let size = CGSize(width: reactionSize.width + spacing + counterSize.width, height: height) + let titleCounterSpacing: CGFloat = 3.0 + + var titleAndCounterSize: CGFloat = titleSize.width + if titleSize.width != 0.0 { + titleAndCounterSize += titleCounterSpacing + } + titleAndCounterSize += counterSize.width + + let size = CGSize(width: reactionSize.width + spacing + titleAndCounterSize - 2.0, height: height) - let iconFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - reactionSize.height) * 0.5)), size: reactionSize) - let counterFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: floor((size.height - counterSize.height) * 0.5)), size: counterSize) + let iconFrame = CGRect(origin: CGPoint(x: -1.0, y: floor((size.height - reactionSize.height) * 0.5)), size: reactionSize) + + let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: floor((size.height - titleSize.height) * 0.5)), size: titleSize) + let counterFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + (titleSize.width.isZero ? 0.0 : titleCounterSpacing), y: floor((size.height - counterSize.height) * 0.5)), size: counterSize) if let iconView = self.icon.view { if iconView.superview == nil { iconView.isUserInteractionEnabled = false - self.addSubview(iconView) + self.containerButton.addSubview(iconView) } iconView.frame = reactionDisplaySize.centered(around: iconFrame.center) + transition.setAlpha(view: iconView, alpha: contentsAlpha) } + if let titleView = self.title.view { + if titleView.superview == nil { + titleView.isUserInteractionEnabled = false + self.containerButton.addSubview(titleView) + } + titleView.frame = titleFrame + transition.setAlpha(view: titleView, alpha: contentsAlpha) + } if let counterView = self.counter.view { if counterView.superview == nil { counterView.isUserInteractionEnabled = false - self.addSubview(counterView) + self.containerButton.addSubview(counterView) } counterView.frame = counterFrame + transition.setAlpha(view: counterView, alpha: contentsAlpha) } - self.background.tintColor = isSelected ? theme.list.itemCheckColors.fillColor : theme.list.controlSecondaryColor + if theme.overallDarkAppearance { + self.background.tintColor = isSelected ? theme.list.itemCheckColors.fillColor : UIColor(white: 1.0, alpha: 0.1) + } else { + self.background.tintColor = isSelected ? theme.list.itemCheckColors.fillColor : theme.rootController.navigationSearchBar.inputFillColor + } if let image = self.background.image { let backgroundFrame = CGRect(origin: CGPoint(x: -6.0, y: floorToScreenPixels((size.height - image.size.height) * 0.5)), size: CGSize(width: size.width + 6.0 + 9.0, height: image.size.height)) transition.setFrame(view: self.background, frame: backgroundFrame) } + transition.setFrame(view: self.containerButton, frame: CGRect(origin: CGPoint(), size: size)) + + self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size) + self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size) + self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) + return size } } @@ -210,10 +441,13 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc private var items: [Item] = [] private var itemViews: [MessageReaction.Reaction: ItemView] = [:] + private var promoView: PromoView? private var itemsDisposable: Disposable? - init(context: AccountContext) { + private var appliedScrollToTag: MemoryBuffer? + + init(context: AccountContext, chatLocation: ChatLocation) { self.context = context self.scrollView = ScrollView(frame: CGRect()) @@ -239,7 +473,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc self.scrollView.disablesInteractiveTransitionGestureRecognizer = true let tagsAndFiles: Signal<([MessageReaction.Reaction: Int], [Int64: TelegramMediaFile]), NoError> = context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId) + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId) ) |> distinctUntilChanged |> mapToSignal { tags -> Signal<([MessageReaction.Reaction: Int], [Int64: TelegramMediaFile]), NoError> in @@ -262,9 +496,10 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc var isFirstUpdate = true self.itemsDisposable = (combineLatest( context.engine.stickers.availableReactions(), + context.engine.stickers.savedMessageTagData(), tagsAndFiles ) - |> deliverOnMainQueue).start(next: { [weak self] availableReactions, tagsAndFiles in + |> deliverOnMainQueue).start(next: { [weak self] availableReactions, savedMessageTags, tagsAndFiles in guard let self else { return } @@ -272,13 +507,15 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc let (tags, files) = tagsAndFiles for (reaction, count) in tags { + let title = savedMessageTags?.tags.first(where: { $0.reaction == reaction })?.title + switch reaction { case .builtin: if let availableReactions { inner: for availableReaction in availableReactions.reactions { if availableReaction.value == reaction { if let file = availableReaction.centerAnimation { - self.items.append(Item(reaction: reaction, count: count, file: file)) + self.items.append(Item(reaction: reaction, count: count, title: title, file: file)) } break inner } @@ -286,7 +523,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc } case let .custom(fileId): if let file = files[fileId] { - self.items.append(Item(reaction: reaction, count: count, file: file)) + self.items.append(Item(reaction: reaction, count: count, title: title, file: file)) } } } @@ -323,17 +560,57 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight, hitTestSlop: 0.0) } + func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, chatController: ChatController) -> LayoutResult { + return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, transition: transition, interfaceState: (chatController as! ChatControllerImpl).presentationInterfaceState) + } + private func update(params: Params, transition: ContainedViewLayoutTransition) { let panelHeight: CGFloat = 39.0 let containerInsets = UIEdgeInsets(top: 0.0, left: params.leftInset + 16.0, bottom: 0.0, right: params.rightInset + 16.0) - let itemSpacing: CGFloat = 26.0 + let itemSpacing: CGFloat = 24.0 var contentSize = CGSize(width: 0.0, height: panelHeight) contentSize.width += containerInsets.left var validIds: [MessageReaction.Reaction] = [] + + let hadItemViews = !self.itemViews.isEmpty var isFirst = true + + if !params.interfaceState.isPremium { + let promoView: PromoView + var itemTransition = transition + if let current = self.promoView { + promoView = current + } else { + itemTransition = .immediate + promoView = PromoView(action: { [weak self] in + guard let self, let interfaceInteraction = self.interfaceInteraction else { + return + } + (interfaceInteraction.chatController() as? ChatControllerImpl)?.presentTagPremiumPaywall() + }) + self.promoView = promoView + self.scrollView.addSubview(promoView) + } + + let itemSize = promoView.update(theme: params.interfaceState.theme, strings: params.interfaceState.strings, height: panelHeight, isUnlock: !self.items.isEmpty, transition: .immediate) + let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize) + + itemTransition.updatePosition(layer: promoView.layer, position: itemFrame.center) + promoView.bounds = CGRect(origin: CGPoint(), size: itemFrame.size) + + contentSize.width += itemSize.width + + isFirst = false + } else { + if let promoView = self.promoView { + self.promoView = nil + promoView.removeFromSuperview() + } + } + for item in self.items { if isFirst { isFirst = false @@ -353,47 +630,88 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc animateIn = true let reaction = item.reaction itemView = ItemView(context: self.context, action: { [weak self] in - guard let self else { + guard let self, let params = self.params else { + return + } + + if !params.interfaceState.isPremium { + if let chatController = self.interfaceInteraction?.chatController() { + (chatController as? ChatControllerImpl)?.presentTagPremiumPaywall() + } return } let tag = ReactionsMessageAttribute.messageTag(reaction: reaction) + var updatedFilter: ChatPresentationInterfaceState.HistoryFilter? + let currentTag = params.interfaceState.historyFilter?.customTag + if currentTag == tag { + updatedFilter = nil + } else { + updatedFilter = ChatPresentationInterfaceState.HistoryFilter(customTag: tag) + } + self.interfaceInteraction?.updateHistoryFilter({ filter in - var tags: [EngineMessage.CustomTag] = filter?.customTags ?? [] - if let index = tags.firstIndex(of: tag) { - tags.remove(at: index) - } else { - tags.append(tag) - } - if tags.isEmpty { - return nil - } else { - return ChatPresentationInterfaceState.HistoryFilter(customTags: tags, isActive: filter?.isActive ?? true) - } + return updatedFilter }) + }, contextGesture: { [weak self] gesture, sourceNode in + guard let self, let params = self.params, let interfaceInteraction = self.interfaceInteraction, let chatController = interfaceInteraction.chatController() else { + gesture.cancel() + return + } + guard let item = self.items.first(where: { $0.reaction == reaction }) else { + gesture.cancel() + return + } + + if !params.interfaceState.isPremium { + (chatController as? ChatControllerImpl)?.presentTagPremiumPaywall() + return + } + + var items: [ContextMenuItem] = [] + + let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }) + items.append(.action(ContextMenuActionItem(text: item.title != nil ? presentationData.strings.Chat_ReactionContextMenu_EditTagLabel : presentationData.strings.Chat_ReactionContextMenu_SetTagLabel, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/TagEditName"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, a in + guard let self else { + a(.default) + return + } + + c.dismiss(completion: { [weak self] in + guard let self, let item = self.items.first(where: { $0.reaction == reaction }) else { + return + } + self.openEditTagTitle(reaction: reaction, hasTitle: item.title != nil) + }) + }))) + + let controller = ContextController(presentationData: presentationData, source: .extracted(TagContextExtractedContentSource(controller: chatController, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) + interfaceInteraction.presentGlobalOverlayController(controller, nil) }) self.itemViews[itemId] = itemView self.scrollView.addSubview(itemView) } - + var isSelected = false if let historyFilter = params.interfaceState.historyFilter { - if historyFilter.customTags.contains(ReactionsMessageAttribute.messageTag(reaction: item.reaction)) { + if historyFilter.customTag == ReactionsMessageAttribute.messageTag(reaction: item.reaction) { isSelected = true } } - let itemSize = itemView.update(item: item, isSelected: isSelected, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate) + let itemSize = itemView.update(item: item, isSelected: isSelected, isLocked: !params.interfaceState.isPremium, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate) let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize) itemTransition.updatePosition(layer: itemView.layer, position: itemFrame.center) + itemTransition.updateBounds(layer: itemView.layer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) + if animateIn && transition.isAnimated { - itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + itemView.layer.animateAlpha(from: 0.0, to: itemView.alpha, duration: 0.15) transition.animateTransformScale(view: itemView, from: 0.001) } - itemView.bounds = CGRect(origin: CGPoint(), size: itemFrame.size) - contentSize.width += itemSize.width } var removedIds: [MessageReaction.Reaction] = [] @@ -424,5 +742,84 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc if self.scrollView.contentSize != contentSize { self.scrollView.contentSize = contentSize } + + let currentFilterTag = params.interfaceState.historyFilter?.customTag + if self.appliedScrollToTag != currentFilterTag { + if let tag = currentFilterTag { + if let reaction = ReactionsMessageAttribute.reactionFromMessageTag(tag: tag), let itemView = self.itemViews[reaction] { + self.appliedScrollToTag = currentFilterTag + self.scrollView.scrollRectToVisible(itemView.frame.insetBy(dx: -46.0, dy: 0.0), animated: hadItemViews) + } + } else { + self.appliedScrollToTag = currentFilterTag + } + } + } + + private func openEditTagTitle(reaction: MessageReaction.Reaction, hasTitle: Bool) { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + + let optionTitle = hasTitle ? presentationData.strings.Chat_EditTagTitle_TitleEdit : presentationData.strings.Chat_EditTagTitle_TitleSet + + let reactionFile: Signal + switch reaction { + case .builtin: + reactionFile = self.context.engine.stickers.availableReactions() + |> take(1) + |> map { availableReactions -> TelegramMediaFile? in + return availableReactions?.reactions.first(where: { $0.value == reaction })?.selectAnimation + } + case let .custom(fileId): + reactionFile = self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId]) + |> map { files -> TelegramMediaFile? in + return files.values.first + } + } + + let _ = (combineLatest( + self.context.engine.stickers.savedMessageTagData(), + reactionFile + ) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] savedMessageTags, reactionFile in + guard let self, let reactionFile, let savedMessageTags else { + return + } + + let promptController = savedTagNameAlertController(context: self.context, updatedPresentationData: nil, text: optionTitle, subtext: presentationData.strings.Chat_EditTagTitle_Text, value: savedMessageTags.tags.first(where: { $0.reaction == reaction })?.title ?? "", reaction: reaction, file: reactionFile, characterLimit: 10, apply: { [weak self] value in + guard let self else { + return + } + + if let value { + let _ = self.context.engine.stickers.setSavedMessageTagTitle(reaction: reaction, title: value.isEmpty ? nil : value).start() + } + }) + self.interfaceInteraction?.presentController(promptController, nil) + }) + } +} + +private final class TagContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool + let ignoreContentTouches: Bool = true + let blurBackground: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center + + private let controller: ViewController + private let sourceNode: ContextExtractedContentContainingNode + + init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool) { + self.controller = controller + self.sourceNode = sourceNode + self.keepInPlace = keepInPlace + } + + func takeView() -> ContextControllerTakeViewInfo? { + return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) } } diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index a8b48d7b7d8..f439084a018 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -14,9 +14,39 @@ import ComponentFlow import MultilineTextComponent import PlainButtonComponent import ComponentDisplayAdapters +import BundleIconComponent +import AnimatedTextComponent private let labelFont = Font.regular(15.0) +private func extractAnimatedTextString(string: PresentationStrings.FormattedString, id: String, mapping: [Int: AnimatedTextComponent.Item.Content]) -> [AnimatedTextComponent.Item] { + var textItems: [AnimatedTextComponent.Item] = [] + + var previousIndex = 0 + let nsString = string.string as NSString + for range in string.ranges.sorted(by: { $0.range.lowerBound < $1.range.lowerBound }) { + if range.range.lowerBound > previousIndex { + textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_before_\(range.index)"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: range.range.lowerBound - previousIndex))))) + } + if let value = mapping[range.index] { + let isUnbreakable: Bool + switch value { + case .text: + isUnbreakable = true + case .number: + isUnbreakable = false + } + textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_item_\(range.index)"), isUnbreakable: isUnbreakable, content: value)) + } + previousIndex = range.range.upperBound + } + if nsString.length > previousIndex { + textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_text_end"), isUnbreakable: true, content: .text(nsString.substring(with: NSRange(location: previousIndex, length: nsString.length - previousIndex))))) + } + + return textItems +} + final class ChatTagSearchInputPanelNode: ChatInputPanelNode { private struct Params: Equatable { var width: CGFloat @@ -54,11 +84,17 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } } - private let button = ComponentView() + private let calendarButton = ComponentView() + private var membersButton: ComponentView? + private var resultsText: ComponentView? + private var listModeButton: ComponentView? + + private var isUpdating: Bool = false - private var params: Params? private var currentLayout: Layout? + private var tagMessageCount: (tag: MemoryBuffer, count: Int?, disposable: Disposable?)? + override var interfaceInteraction: ChatPanelInterfaceInteraction? { didSet { } @@ -69,6 +105,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } deinit { + self.tagMessageCount?.disposable?.dispose() } 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 { @@ -82,8 +119,121 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { return height } + + func prepareSwitchToFilter(tag: MemoryBuffer, count: Int) { + self.tagMessageCount?.disposable?.dispose() + self.tagMessageCount = (tag, count, nil) + } + + private func update(transition: Transition) { + if self.isUpdating { + return + } + if let params = self.currentLayout?.params { + let _ = self.update(params: params, transition: transition) + } + } private func update(params: Params, transition: Transition) -> CGFloat { + self.isUpdating = true + defer { + self.isUpdating = false + } + + if let historyFilter = params.interfaceState.historyFilter, let reaction = ReactionsMessageAttribute.reactionFromMessageTag(tag: historyFilter.customTag) { + let tag = historyFilter.customTag + + if let current = self.tagMessageCount, current.tag == tag { + } else { + self.tagMessageCount = (tag, nil, nil) + } + + if self.tagMessageCount?.disposable == nil { + if let context = self.context { + self.tagMessageCount?.disposable = (context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.ReactionTagMessageCount(peerId: context.account.peerId, threadId: params.interfaceState.chatLocation.threadId, reaction: reaction) + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] count in + guard let self else { + return + } + if self.tagMessageCount?.tag == tag { + if self.tagMessageCount?.count != count { + self.tagMessageCount?.count = count + self.update(transition: .easeInOut(duration: 0.25)) + } + } + }) + } + } + } else { + if let tagMessageCount = self.tagMessageCount { + self.tagMessageCount = nil + tagMessageCount.disposable?.dispose() + } + } + + + var canSearchMembers = false + if let search = params.interfaceState.search { + if case .everything = search.domain { + if let _ = params.interfaceState.renderedPeer?.peer as? TelegramGroup { + canSearchMembers = true + } else if let peer = params.interfaceState.renderedPeer?.peer as? TelegramChannel, case .group = peer.info { + canSearchMembers = true + } + } else { + canSearchMembers = false + } + } + let displaySearchMembers = (params.interfaceState.search?.query.isEmpty ?? true) && canSearchMembers + + var canChangeListMode = false + + var resultsTextString: [AnimatedTextComponent.Item] = [] + if let results = params.interfaceState.search?.resultsState { + let displayTotalCount = results.completed ? results.messageIndices.count : Int(results.totalCount) + if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) { + canChangeListMode = true + + if params.interfaceState.displayHistoryFilterAsList { + resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat( + ".", + "." + ), id: "total_count", mapping: [ + 0: .number(displayTotalCount, minDigits: 1), + 1: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCount(Int32(displayTotalCount))) + ]) + } else { + let adjustedIndex = results.messageIndices.count - 1 - index + + resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Items_NOfM( + ".", + "." + ), id: "position", mapping: [ + 0: .number(adjustedIndex + 1, minDigits: 1), + 1: .number(displayTotalCount, minDigits: 1) + ]) + } + } else { + canChangeListMode = false + + resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_no_results"), isUnbreakable: true, content: .text(params.interfaceState.strings.Conversation_SearchNoResults))) + } + } else if let count = self.tagMessageCount?.count { + canChangeListMode = count != 0 + + resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat( + ".", + "." + ), id: "total_count", mapping: [ + 0: .number(count, minDigits: 1), + 1: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCount(Int32(count))) + ]) + } else if let context = self.context, case .peer(context.account.peerId) = params.interfaceState.chatLocation { + canChangeListMode = true + } + let height: CGFloat if case .regular = params.metrics.widthClass { height = 49.0 @@ -91,43 +241,198 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { height = 45.0 } - let buttonTitle: String - if let historyFilter = params.interfaceState.historyFilter, historyFilter.isActive { - buttonTitle = params.interfaceState.strings.Chat_TagSearchShowMessages - } else { - buttonTitle = params.interfaceState.strings.Chat_TagSearchHideMessages - } + var modeButtonTitle: [AnimatedTextComponent.Item] = [] + modeButtonTitle = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeFormat("."), id: "mode", mapping: [ + 0: params.interfaceState.displayHistoryFilterAsList ? .text(params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeChat) : .text(params.interfaceState.strings.Chat_BottomSearchPanel_DisplayModeList) + ]) let size = CGSize(width: params.width - params.additionalSideInsets.left * 2.0 - params.leftInset * 2.0, height: height) - let buttonSize = self.button.update( + + if canChangeListMode { + var listModeButtonTransition = transition + let listModeButton: ComponentView + if let current = self.listModeButton { + listModeButton = current + } else { + listModeButtonTransition = listModeButtonTransition.withAnimation(.none) + listModeButton = ComponentView() + self.listModeButton = listModeButton + } + + let buttonSize = listModeButton.update( + transition: listModeButtonTransition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(AnimatedTextComponent( + font: Font.regular(15.0), + color: params.interfaceState.theme.rootController.navigationBar.accentTextColor, + items: modeButtonTitle + )), + effectAlignment: .right, + minSize: CGSize(width: 1.0, height: size.height), + contentInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), + action: { [weak self] in + guard let self, let params = self.currentLayout?.params else { + return + } + self.interfaceInteraction?.updateDisplayHistoryFilterAsList(!params.interfaceState.displayHistoryFilterAsList) + }, + animateScale: false, + animateContents: true + )), + environment: {}, + containerSize: size + ) + if let buttonView = listModeButton.view { + if buttonView.superview == nil { + buttonView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.5) + buttonView.alpha = 0.0 + self.view.addSubview(buttonView) + } + let listModeFrame = CGRect(origin: CGPoint(x: size.width - params.rightInset - 11.0 - buttonSize.width, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize) + listModeButtonTransition.setPosition(view: buttonView, position: CGPoint(x: listModeFrame.minX + listModeFrame.width * buttonView.layer.anchorPoint.x, y: listModeFrame.minY + listModeFrame.height * buttonView.layer.anchorPoint.y)) + listModeButtonTransition.setBounds(view: buttonView, bounds: CGRect(origin: CGPoint(), size: listModeFrame.size)) + transition.setAlpha(view: buttonView, alpha: 1.0) + } + } else { + if let listModeButton = self.listModeButton { + self.listModeButton = nil + if let listModeButtonView = listModeButton.view { + transition.setAlpha(view: listModeButtonView, alpha: 0.0, completion: { [weak listModeButtonView] _ in + listModeButtonView?.removeFromSuperview() + }) + } + } + } + + var nextLeftX: CGFloat = 12.0 + + let calendarButtonSize = self.calendarButton.update( transition: .immediate, component: AnyComponent(PlainButtonComponent( - content: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: buttonTitle, font: Font.regular(17.0), textColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor)) + content: AnyComponent(BundleIconComponent( + name: "Chat/Input/Search/Calendar", + tintColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor )), effectAlignment: .center, - minSize: size, + minSize: CGSize(width: 1.0, height: size.height), + contentInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), action: { [weak self] in guard let self else { return } - self.interfaceInteraction?.updateHistoryFilter { filter in - guard var filter else { - return nil - } - filter.isActive = !filter.isActive - return filter - } + self.interfaceInteraction?.openCalendarSearch() } )), environment: {}, containerSize: size ) - if let buttonView = self.button.view { - if buttonView.superview == nil { - self.view.addSubview(buttonView) + let calendarButtonFrame = CGRect(origin: CGPoint(x: nextLeftX, y: floor((size.height - calendarButtonSize.height) * 0.5)), size: calendarButtonSize) + if let calendarButtonView = self.calendarButton.view { + if calendarButtonView.superview == nil { + self.view.addSubview(calendarButtonView) + } + transition.setFrame(view: calendarButtonView, frame: calendarButtonFrame) + } + nextLeftX += calendarButtonSize.width + 8.0 + + if displaySearchMembers { + let membersButton: ComponentView + if let current = self.membersButton { + membersButton = current + } else { + membersButton = ComponentView() + self.membersButton = membersButton + } + + let buttonSize = membersButton.update( + transition: .immediate, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(BundleIconComponent( + name: "Chat/Input/Search/Members", + tintColor: params.interfaceState.theme.rootController.navigationBar.accentTextColor + )), + effectAlignment: .center, + minSize: CGSize(width: 1.0, height: size.height), + contentInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), + action: { [weak self] in + guard let self else { + return + } + self.interfaceInteraction?.toggleMembersSearch(true) + } + )), + environment: {}, + containerSize: size + ) + if let buttonView = membersButton.view { + var membersButtonTransition = transition + var animateIn = false + if buttonView.superview == nil { + membersButtonTransition = membersButtonTransition.withAnimation(.none) + buttonView.alpha = 0.0 + animateIn = true + self.view.addSubview(buttonView) + } + membersButtonTransition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: nextLeftX, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize)) + + transition.setAlpha(view: buttonView, alpha: 1.0) + if animateIn { + transition.animateScale(view: buttonView, from: 0.001, to: 1.0) + } + } + nextLeftX += buttonSize.width + 8.0 + } else { + if let membersButton = self.membersButton { + self.membersButton = nil + if let membersButtonView = membersButton.view { + transition.setAlpha(view: membersButtonView, alpha: 0.0, completion: { [weak membersButtonView] _ in + membersButtonView?.removeFromSuperview() + }) + transition.setScale(view: membersButtonView, scale: 0.001) + } + } + } + + if !resultsTextString.isEmpty { + var resultsTextTransition = transition + let resultsText: ComponentView + if let current = self.resultsText { + resultsText = current + } else { + resultsTextTransition = resultsTextTransition.withAnimation(.none) + resultsText = ComponentView() + self.resultsText = resultsText + } + + let resultsTextSize = resultsText.update( + transition: resultsTextTransition, + component: AnyComponent(AnimatedTextComponent( + font: Font.regular(15.0), + color: params.interfaceState.theme.rootController.navigationBar.secondaryTextColor, + items: resultsTextString + )), + environment: {}, + containerSize: size + ) + let resultsTextFrame = CGRect(origin: CGPoint(x: nextLeftX - 3.0, y: floor((size.height - resultsTextSize.height) * 0.5)), size: resultsTextSize) + if let resultsTextView = resultsText.view { + if resultsTextView.superview == nil { + resultsTextView.alpha = 0.0 + self.view.addSubview(resultsTextView) + } + resultsTextTransition.setFrame(view: resultsTextView, frame: resultsTextFrame) + transition.setAlpha(view: resultsTextView, alpha: 1.0) + } + nextLeftX += -3.0 + resultsTextSize.width + } else { + if let resultsText = self.resultsText { + self.resultsText = nil + if let resultsTextView = resultsText.view { + transition.setAlpha(view: resultsTextView, alpha: 0.0, completion: { [weak resultsTextView] _ in + resultsTextView?.removeFromSuperview() + }) + } } - transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(), size: buttonSize)) } return height diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index c4b0fb3bbd3..f10a846f942 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1839,7 +1839,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } else { if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) { placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder - } else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation, !replyThreadMessage.isForumPost { + } else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation, !replyThreadMessage.isForumPost, replyThreadMessage.peerId != self.context?.account.peerId { if replyThreadMessage.isChannelPost { placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment } else { diff --git a/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift index f7973f8c493..8fd917a6571 100644 --- a/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift @@ -3,13 +3,10 @@ import UIKit import Display import AsyncDisplayKit import ChatPresentationInterfaceState +import AccountContext class ChatTitleAccessoryPanelNode: ASDisplayNode { - struct LayoutResult { - var backgroundHeight: CGFloat - var insetHeight: CGFloat - var hitTestSlop: CGFloat - } + typealias LayoutResult = ChatControllerCustomNavigationPanelNode.LayoutResult var interfaceInteraction: ChatPanelInterfaceInteraction? diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index cb8f91145c9..18cb29e417d 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -150,7 +150,6 @@ final class ManagedAudioRecorderContext { private let beganWithTone: (Bool) -> Void private var paused = true - private var manuallyPaused = false private let queue: Queue private let mediaManager: MediaManager @@ -414,11 +413,9 @@ final class ManagedAudioRecorderContext { return Signal { subscriber in queue.async { if let strongSelf = self { - if !strongSelf.manuallyPaused { - strongSelf.hasAudioSession = false - strongSelf.stop() - strongSelf.recordingState.set(.stopped) - } + strongSelf.hasAudioSession = false + strongSelf.stop() + strongSelf.recordingState.set(.stopped) subscriber.putCompletion() } } @@ -453,17 +450,13 @@ final class ManagedAudioRecorderContext { func pause() { assert(self.queue.isCurrent()) - self.manuallyPaused = true + self.stop() } func resume() { assert(self.queue.isCurrent()) - if self.manuallyPaused { - self.manuallyPaused = false - } else if self.paused { - self.start() - } + self.start() } func stop() { @@ -507,7 +500,7 @@ final class ManagedAudioRecorderContext { free(buffer.mData) } - if !self.processSamples || self.manuallyPaused { + if !self.processSamples { return } diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index cddcdac8277..9dc0e6f4c25 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -41,6 +41,13 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam return false } } + } else if case let .peer(peer) = params.chatLocation, peer.id == params.context.account.peerId { + viewForumAsMessages = params.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.DisplaySavedChatsAsTopics() + ) + |> map { value in + return !value + } } let _ = (viewForumAsMessages @@ -93,6 +100,14 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam return } + if !params.forceOpenChat, !viewForumAsMessages, params.subject == nil, case let .peer(peer) = params.chatLocation, peer.id == params.context.account.peerId { + if let controller = params.context.sharedContext.makePeerInfoController(context: params.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + params.navigationController.pushViewController(controller, animated: params.animated, completion: { + }) + return + } + } + var found = false var isFirst = true if params.useExisting { diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index f84afb561ef..b22296c21a1 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -127,7 +127,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { return true } - if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) { + if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatFilterTag: params.chatFilterTag, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) { switch mediaData { case let .url(url): params.openUrl(url) diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift index 0ef19eeee87..e96f9c4833f 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift @@ -114,7 +114,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer guard let navigationController = self.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) }) } return false diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 7ae4e54e012..07130e38ac7 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -78,7 +78,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, openPeerMention: { _, _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in - }, updateMessageReaction: { _, _ in + }, updateMessageReaction: { _, _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _, _ in @@ -304,7 +304,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location { playlistLocation = .custom(messages: messages, at: id, loadMore: loadMore) } - return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation)) + return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation)) } return false } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 52454b8ae60..b0567c6ae4b 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1626,12 +1626,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { openExternalUrlImpl(context: context, urlContext: urlContext, url: url, forceExternal: forceExternal, presentationData: presentationData, navigationController: navigationController, dismissInput: dismissInput) } - public func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set) -> Signal { - return chatAvailableMessageActionsImpl(engine: engine, accountPeerId: accountPeerId, messageIds: messageIds) + public func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, keepUpdated: Bool) -> Signal { + return chatAvailableMessageActionsImpl(engine: engine, accountPeerId: accountPeerId, messageIds: messageIds, keepUpdated: keepUpdated) } public func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, messages: [EngineMessage.Id: EngineMessage] = [:], peers: [EnginePeer.Id: EnginePeer] = [:]) -> Signal { - return chatAvailableMessageActionsImpl(engine: engine, accountPeerId: accountPeerId, messageIds: messageIds, messages: messages.mapValues({ $0._asMessage() }), peers: peers.mapValues({ $0._asPeer() })) + return chatAvailableMessageActionsImpl(engine: engine, accountPeerId: accountPeerId, messageIds: messageIds, messages: messages.mapValues({ $0._asMessage() }), peers: peers.mapValues({ $0._asPeer() }), keepUpdated: false) } public func navigateToChatController(_ params: NavigateToChatControllerParams) { @@ -1814,7 +1814,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _, _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in - }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in + }, updateMessageReaction: { _, _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _, _ in }, navigateToMessageStandalone: { _ in }, navigateToThreadMessage: { _, _, _ in }, tapMessage: { message in @@ -1904,7 +1904,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { chatLocation = .peer(id: messages.first!.id.peerId) } - return ChatMessageItemImpl(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: isPreview), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: availableReactions, defaultReaction: nil, isPremium: false, accountPeer: accountPeer.flatMap(EnginePeer.init), forceInlineReactions: true, isStandalone: isStandalone), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil) + return ChatMessageItemImpl(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: isPreview), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: availableReactions, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: accountPeer.flatMap(EnginePeer.init), forceInlineReactions: true, isStandalone: isStandalone), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil) } public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader { @@ -2071,6 +2071,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .storiesExpirationDurations case .storiesSuggestedReactions: mappedSource = .storiesSuggestedReactions + case .storiesHigherQuality: + mappedSource = .storiesHigherQuality case let .channelBoost(peerId): mappedSource = .channelBoost(peerId) case .nameColor: @@ -2083,6 +2085,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .presence case .readTime: mappedSource = .readTime + case .messageTags: + mappedSource = .messageTags } let controller = PremiumIntroScreen(context: context, modal: modal, source: mappedSource, forceDark: forceDark) controller.wasDismissed = dismissed @@ -2126,6 +2130,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .colors case .wallpapers: mappedSubject = .wallpapers + case .messageTags: + mappedSubject = .messageTags } return PremiumDemoScreen(context: context, subject: mappedSubject, action: action) } diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index 2cb7f8f0908..6e633e5a4eb 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -55,7 +55,6 @@ public struct ExperimentalUISettings: Codable, Equatable { public var crashOnMemoryPressure: Bool public var dustEffect: Bool public var callV2: Bool - public var alternativeStoryMedia: Bool public var allowWebViewInspection: Bool public static var defaultSettings: ExperimentalUISettings { @@ -91,7 +90,6 @@ public struct ExperimentalUISettings: Codable, Equatable { crashOnMemoryPressure: false, dustEffect: false, callV2: false, - alternativeStoryMedia: false, allowWebViewInspection: false ) } @@ -127,7 +125,6 @@ public struct ExperimentalUISettings: Codable, Equatable { crashOnMemoryPressure: Bool, dustEffect: Bool, callV2: Bool, - alternativeStoryMedia: Bool, allowWebViewInspection: Bool ) { self.keepChatNavigationStack = keepChatNavigationStack @@ -160,7 +157,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.crashOnMemoryPressure = crashOnMemoryPressure self.dustEffect = dustEffect self.callV2 = callV2 - self.alternativeStoryMedia = alternativeStoryMedia self.allowWebViewInspection = allowWebViewInspection } @@ -197,7 +193,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.crashOnMemoryPressure = try container.decodeIfPresent(Bool.self, forKey: "crashOnMemoryPressure") ?? false self.dustEffect = try container.decodeIfPresent(Bool.self, forKey: "dustEffect") ?? false self.callV2 = try container.decodeIfPresent(Bool.self, forKey: "callV2") ?? false - self.alternativeStoryMedia = try container.decodeIfPresent(Bool.self, forKey: "alternativeStoryMedia") ?? false self.allowWebViewInspection = try container.decodeIfPresent(Bool.self, forKey: "allowWebViewInspection") ?? false } @@ -234,7 +229,6 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode(self.crashOnMemoryPressure, forKey: "crashOnMemoryPressure") try container.encode(self.dustEffect, forKey: "dustEffect") try container.encode(self.callV2, forKey: "callV2") - try container.encode(self.alternativeStoryMedia, forKey: "alternativeStoryMedia") try container.encode(self.allowWebViewInspection, forKey: "allowWebViewInspection") } } diff --git a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift index 7f9abd7acf2..74c5eced057 100644 --- a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift @@ -369,8 +369,8 @@ public struct MediaAutoDownloadSettings: Codable, Equatable { public var wifi: MediaAutoDownloadConnection public var downloadInBackground: Bool - public var energyUsageSettings: EnergyUsageSettings + public var highQualityStories: Bool public static var defaultSettings: MediaAutoDownloadSettings { let mb: Int64 = 1024 * 1024 @@ -397,15 +397,16 @@ public struct MediaAutoDownloadSettings: Codable, Equatable { stories: MediaAutoDownloadCategory(contacts: true, otherPrivate: true, groups: true, channels: true, sizeLimit: 20 * mb, predownload: false) ) ) - return MediaAutoDownloadSettings(presets: presets, cellular: MediaAutoDownloadConnection(enabled: true, preset: .medium, custom: nil), wifi: MediaAutoDownloadConnection(enabled: true, preset: .high, custom: nil), downloadInBackground: true, energyUsageSettings: EnergyUsageSettings.default) + return MediaAutoDownloadSettings(presets: presets, cellular: MediaAutoDownloadConnection(enabled: true, preset: .medium, custom: nil), wifi: MediaAutoDownloadConnection(enabled: true, preset: .high, custom: nil), downloadInBackground: true, energyUsageSettings: EnergyUsageSettings.default, highQualityStories: false) } - public init(presets: MediaAutoDownloadPresets, cellular: MediaAutoDownloadConnection, wifi: MediaAutoDownloadConnection, downloadInBackground: Bool, energyUsageSettings: EnergyUsageSettings) { + public init(presets: MediaAutoDownloadPresets, cellular: MediaAutoDownloadConnection, wifi: MediaAutoDownloadConnection, downloadInBackground: Bool, energyUsageSettings: EnergyUsageSettings, highQualityStories: Bool) { self.presets = presets self.cellular = cellular self.wifi = wifi self.downloadInBackground = downloadInBackground self.energyUsageSettings = energyUsageSettings + self.highQualityStories = highQualityStories } public init(from decoder: Decoder) throws { @@ -419,8 +420,8 @@ public struct MediaAutoDownloadSettings: Codable, Equatable { self.wifi = (try? container.decodeIfPresent(MediaAutoDownloadConnection.self, forKey: "wifi")) ?? defaultSettings.wifi self.downloadInBackground = try container.decode(Int32.self, forKey: "downloadInBackground") != 0 - self.energyUsageSettings = (try container.decodeIfPresent(EnergyUsageSettings.self, forKey: "energyUsageSettings")) ?? EnergyUsageSettings.default + self.highQualityStories = try container.decodeIfPresent(Bool.self, forKey: "highQualityStories") ?? false } public func encode(to encoder: Encoder) throws { @@ -430,14 +431,15 @@ public struct MediaAutoDownloadSettings: Codable, Equatable { try container.encode(self.wifi, forKey: "wifi") try container.encode((self.downloadInBackground ? 1 : 0) as Int32, forKey: "downloadInBackground") try container.encode(self.energyUsageSettings, forKey: "energyUsageSettings") + try container.encode(self.highQualityStories, forKey: "highQualityStories") } public func connectionSettings(for networkType: MediaAutoDownloadNetworkType) -> MediaAutoDownloadConnection { switch networkType { - case .cellular: - return self.cellular - case .wifi: - return self.wifi + case .cellular: + return self.cellular + case .wifi: + return self.wifi } } diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index f8fa7d5abfd..eaeda817df1 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -377,6 +377,8 @@ public final class OngoingGroupCallContext { } public enum Buffer { + case argb(NativeBuffer) + case bgra(NativeBuffer) case native(NativeBuffer) case nv12(NV12Buffer) case i420(I420Buffer) @@ -392,7 +394,13 @@ public final class OngoingGroupCallContext { init(frameData: CallVideoFrameData) { if let nativeBuffer = frameData.buffer as? CallVideoFrameNativePixelBuffer { - self.buffer = .native(NativeBuffer(pixelBuffer: nativeBuffer.pixelBuffer)) + if CVPixelBufferGetPixelFormatType(nativeBuffer.pixelBuffer) == kCVPixelFormatType_32ARGB { + self.buffer = .argb(NativeBuffer(pixelBuffer: nativeBuffer.pixelBuffer)) + } else if CVPixelBufferGetPixelFormatType(nativeBuffer.pixelBuffer) == kCVPixelFormatType_32BGRA { + self.buffer = .bgra(NativeBuffer(pixelBuffer: nativeBuffer.pixelBuffer)) + } else { + self.buffer = .native(NativeBuffer(pixelBuffer: nativeBuffer.pixelBuffer)) + } } else if let nv12Buffer = frameData.buffer as? CallVideoFrameNV12Buffer { self.buffer = .nv12(NV12Buffer(wrapped: nv12Buffer)) } else if let i420Buffer = frameData.buffer as? CallVideoFrameI420Buffer { diff --git a/submodules/UndoUI/BUILD b/submodules/UndoUI/BUILD index 369f0e7bcf0..ce70e46883b 100644 --- a/submodules/UndoUI/BUILD +++ b/submodules/UndoUI/BUILD @@ -29,6 +29,7 @@ swift_library( "//submodules/ComponentFlow:ComponentFlow", "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/TextNodeWithEntities", ], visibility = [ "//visibility:public", diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index d63b3b5c3ea..01fabaa0acb 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -45,6 +45,7 @@ public enum UndoOverlayContent { case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?, timeout: Double?) case premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?) case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?) + case messageTagged(context: AccountContext, isSingleMessage: Bool, customEmoji: TelegramMediaFile, isBuiltinReaction: Bool, customUndoText: String?) } public enum UndoOverlayAction { @@ -53,6 +54,22 @@ public enum UndoOverlayAction { case commit } +public final class UndoOverlayControllerAdditionalViewInteraction { + public let disableTimeout: () -> Void + public let dismiss: () -> Void + + public init(disableTimeout: @escaping () -> Void, dismiss: @escaping () -> Void) { + self.disableTimeout = disableTimeout + self.dismiss = dismiss + } +} + +public protocol UndoOverlayControllerAdditionalView: UIView { + var interaction: UndoOverlayControllerAdditionalViewInteraction? { get set } + + func update(size: CGSize, transition: ContainedViewLayoutTransition) +} + public final class UndoOverlayController: ViewController { public enum Position { case top @@ -69,6 +86,7 @@ public final class UndoOverlayController: ViewController { private let position: Position private let animateInAsReplacement: Bool private var action: (UndoOverlayAction) -> Bool + private let additionalView: (() -> UndoOverlayControllerAdditionalView?)? private let blurred: Bool private var didPlayPresentationAnimation = false @@ -78,7 +96,7 @@ public final class UndoOverlayController: ViewController { public var tag: Any? - public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, position: Position = .bottom, animateInAsReplacement: Bool = false, blurred: Bool = false, action: @escaping (UndoOverlayAction) -> Bool) { + public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, position: Position = .bottom, animateInAsReplacement: Bool = false, blurred: Bool = false, action: @escaping (UndoOverlayAction) -> Bool, additionalView: (() -> UndoOverlayControllerAdditionalView?)? = nil) { self.presentationData = presentationData self.content = content self.elevatedLayout = elevatedLayout @@ -86,6 +104,7 @@ public final class UndoOverlayController: ViewController { self.animateInAsReplacement = animateInAsReplacement self.blurred = blurred self.action = action + self.additionalView = additionalView super.init(navigationBarPresentationData: nil) @@ -97,7 +116,7 @@ public final class UndoOverlayController: ViewController { } override public func loadDisplayNode() { - self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, placementPosition: self.position, blurred: self.blurred, action: { [weak self] value in + self.displayNode = UndoOverlayControllerNode(presentationData: self.presentationData, content: self.content, elevatedLayout: self.elevatedLayout, placementPosition: self.position, blurred: self.blurred, additionalView: self.additionalView, action: { [weak self] value in return self?.action(value) ?? false }, dismiss: { [weak self] in self?.dismiss() diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 509cf77fe8d..16b60c0dade 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -19,6 +19,7 @@ import AccountContext import AnimatedAvatarSetNode import ComponentFlow import EmojiStatusComponent +import TextNodeWithEntities final class UndoOverlayControllerNode: ViewControllerTracingNode { private let presentationData: PresentationData @@ -40,7 +41,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private var stickerOffset: CGPoint? private var emojiStatus: ComponentView? private let titleNode: ImmediateTextNode - private let textNode: ImmediateTextNode + private let textNode: ImmediateTextNodeWithEntities private let buttonNode: HighlightTrackingButtonNode private let undoButtonTextNode: ImmediateTextNode private let undoButtonNode: HighlightTrackingButtonNode @@ -52,10 +53,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private var content: UndoOverlayContent private let blurred: Bool + private let additionalView: UndoOverlayControllerAdditionalView? + private let effectView: UIView private let animationBackgroundColor: UIColor + private var isTimeoutDisabled: Bool = false private var originalRemainingSeconds: Double private var remainingSeconds: Double private var timer: SwiftSignalKit.Timer? @@ -64,13 +68,15 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private var fetchResourceDisposable: Disposable? - init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, blurred: Bool, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) { + init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, blurred: Bool, additionalView: (() -> UndoOverlayControllerAdditionalView?)?, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) { self.presentationData = presentationData self.elevatedLayout = elevatedLayout self.placementPosition = placementPosition self.blurred = blurred self.content = content + self.additionalView = additionalView?() + self.action = action self.dismiss = dismiss @@ -81,7 +87,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.titleNode.displaysAsynchronously = false self.titleNode.maximumNumberOfLines = 0 - self.textNode = ImmediateTextNode() + self.textNode = ImmediateTextNodeWithEntities() self.textNode.displaysAsynchronously = false self.textNode.maximumNumberOfLines = 0 @@ -1153,6 +1159,47 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } else { displayUndo = false } + case let .messageTagged(context, isSingleMessage, customEmoji, isBuiltinReaction, customUndoText): + self.avatarNode = nil + self.iconNode = nil + self.iconCheckNode = nil + self.animationNode = AnimationNode(animation: "anim_savedmessages", colors: [:], scale: 0.066) + self.animatedStickerNode = nil + + let rawText = isSingleMessage ? presentationData.strings.Chat_ToastMessageTagged_Text(".") : presentationData.strings.Chat_ToastMessagesTagged_Text(".") + let attributedText = NSMutableAttributedString(string: rawText.string, font: Font.regular(14.0), textColor: .white) + for range in rawText.ranges { + attributedText.addAttributes([ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: customEmoji.fileId.id, file: customEmoji, custom: nil)], range: range.range) + } + self.textNode.customItemLayout = { size, _ in + if isBuiltinReaction { + return CGSize(width: size.width * 2.0, height: size.height * 2.0) + } + return size + } + + self.textNode.arguments = TextNodeWithEntities.Arguments( + context: context, + cache: context.animationCache, + renderer: context.animationRenderer, + placeholderColor: UIColor(white: 1.0, alpha: 0.1), + attemptSynchronous: false + ) + self.textNode.visibility = true + + self.textNode.attributedText = attributedText + self.textNode.maximumNumberOfLines = 2 + + displayUndo = false + self.originalRemainingSeconds = 3 + + if let customUndoText = customUndoText { + undoText = customUndoText + displayUndo = true + isUserInteractionEnabled = true + } else { + displayUndo = false + } } self.remainingSeconds = self.originalRemainingSeconds @@ -1181,7 +1228,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { switch content { case .removedChat: self.panelWrapperNode.addSubnode(self.timerTextNode) - case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers: + case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .premiumPaywall, .peers, .messageTagged: if self.textNode.tapAttributeAction != nil || displayUndo { self.isUserInteractionEnabled = true } else { @@ -1251,6 +1298,24 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.animatedStickerNode?.started = { [weak self] in self?.stillStickerNode?.isHidden = true } + + if let additionalView = self.additionalView { + additionalView.interaction = UndoOverlayControllerAdditionalViewInteraction(disableTimeout: { [weak self] in + guard let self else { + return + } + self.isTimeoutDisabled = true + self.timer?.invalidate() + self.remainingSeconds = self.originalRemainingSeconds + self.checkTimer() + }, dismiss: { [weak self] in + guard let self else { + return + } + self.dismiss() + }) + self.view.addSubview(additionalView) + } } deinit { @@ -1265,6 +1330,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } self.panelNode.view.addSubview(self.effectView) + + if self.additionalView != nil { + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.dismiss() + } } @objc private func buttonPressed() { @@ -1318,11 +1393,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.containerLayoutUpdated(layout: validLayout, transition: .immediate) } } - let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: false, completion: { [weak self] in - self?.checkTimer() - }, queue: .mainQueue()) - self.timer = timer - timer.start() + if !self.isTimeoutDisabled { + let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: false, completion: { [weak self] in + self?.checkTimer() + }, queue: .mainQueue()) + self.timer = timer + timer.start() + } } } @@ -1566,6 +1643,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let avatarsFrame = CGRect(origin: CGPoint(x: 13.0, y: floor((contentHeight - multiAvatarsSize.height) / 2.0) + verticalOffset), size: multiAvatarsSize) transition.updateFrame(node: multiAvatarsNode, frame: avatarsFrame) } + + if let additionalView = self.additionalView { + let additionalViewFrame = CGRect(origin: CGPoint(x: 0.0, y: panelWrapperFrame.maxY), size: CGSize(width: layout.size.width, height: layout.size.height - insets.bottom - panelWrapperFrame.maxY)) + transition.updateFrame(view: additionalView, frame: additionalViewFrame) + additionalView.update(size: additionalViewFrame.size, transition: transition) + } } func animateIn(asReplacement: Bool) { @@ -1602,6 +1685,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.animatedStickerNode?.visibility = true + if let additionalView = self.additionalView { + additionalView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + self.checkTimer() } @@ -1617,6 +1704,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } self.panelNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) self.panelWrapperNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + + if let additionalView = self.additionalView { + additionalView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + } } func animateOutWithReplacement(completion: @escaping () -> Void) { @@ -1631,9 +1722,24 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let additionalView = self.additionalView { + if let result = additionalView.hitTest(self.view.convert(point, to: additionalView), with: event) { + return result + } + } + if !self.panelNode.frame.insetBy(dx: -60.0, dy: 0.0).contains(point) { - return nil + if self.additionalView != nil && self.isTimeoutDisabled { + } else { + return nil + } + } + let result = super.hitTest(point, with: event) + if result == self { + if self.additionalView != nil && self.isTimeoutDisabled { + return self.view + } } - return super.hitTest(point, with: event) + return result } } diff --git a/swift_deps.bzl b/swift_deps.bzl index 79657119f48..686062aac8e 100644 --- a/swift_deps.bzl +++ b/swift_deps.bzl @@ -36,7 +36,7 @@ def swift_dependencies(): # branch: develop swift_package( name = "swiftpkg_nicegram_assistant_ios", - commit = "f1b83c87b7c0da94adad36a17d36b124e98b86c7", + commit = "6e15969c39005fa75df34bb7bf0d77e433490d39", dependencies_index = "@//:swift_deps_index.json", remote = "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", ) diff --git a/swift_deps_index.json b/swift_deps_index.json index 9fca603ad36..c4774efc47f 100644 --- a/swift_deps_index.json +++ b/swift_deps_index.json @@ -127,6 +127,16 @@ "nicegram-assistant" ] }, + { + "name": "FeatEsimSplash", + "c99name": "FeatEsimSplash", + "src_type": "swift", + "label": "@swiftpkg_nicegram_assistant_ios//:Sources_FeatEsimSplash", + "package_identity": "nicegram-assistant-ios", + "product_memberships": [ + "nicegram-assistant" + ] + }, { "name": "FeatHiddenChats", "c99name": "FeatHiddenChats", @@ -850,7 +860,7 @@ "name": "swiftpkg_nicegram_assistant_ios", "identity": "nicegram-assistant-ios", "remote": { - "commit": "f1b83c87b7c0da94adad36a17d36b124e98b86c7", + "commit": "6e15969c39005fa75df34bb7bf0d77e433490d39", "remote": "git@bitbucket.org:mobyrix/nicegram-assistant-ios.git", "branch": "develop" } diff --git a/versions.json b/versions.json index 783812bf6bf..719dcd626a7 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "1.5.4", + "app": "1.5.5", "bazel": "6.4.0", "xcode": "15.1", "macos": "13.0"