diff --git a/.bazelrc b/.bazelrc index fc6d0486d1..53cb710dc1 100644 --- a/.bazelrc +++ b/.bazelrc @@ -6,26 +6,11 @@ build --apple_crosstool_top=@local_config_apple_cc//:toolchain build --crosstool_top=@local_config_apple_cc//:toolchain build --host_crosstool_top=@local_config_apple_cc//:toolchain -build --cxxopt='-std=c++17' -build --per_file_copt="third-party/webrtc/.*\.cpp$","@-std=c++17" -build --per_file_copt="third-party/webrtc/.*\.cc$","@-std=c++17" -build --per_file_copt="third-party/webrtc/.*\.mm$","@-std=c++17" -build --per_file_copt="submodules/LottieMeshSwift/LottieMeshBinding/Sources/.*\.mm$","@-std=c++17" -build --per_file_copt="submodules/LottieCpp/lottiecpp/Sources/.*\.mm$","@-std=c++17" -build --per_file_copt="submodules/LottieCpp/lottiecpp/Sources/.*\.cpp$","@-std=c++17" -build --per_file_copt="submodules/LottieCpp/lottiecpp/PlatformSpecific/Darwin/Sources/.*\.mm$","@-std=c++17" -build --per_file_copt="submodules/LottieCpp/lottiecpp/PlatformSpecific/Darwin/Sources/.*\.cpp$","@-std=c++17" -build --per_file_copt="Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/.*\.cpp$","@-std=c++17" -build --per_file_copt="Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/.*\.mm$","@-std=c++17" -build --per_file_copt="third-party/td/TdBinding/Sources/.*\.mm$","@-std=c++17" - #build --swiftcopt=-whole-module-optimization build --per_file_copt=".*\.m$","@-fno-objc-msgsend-selector-stubs" build --per_file_copt=".*\.mm$","@-fno-objc-msgsend-selector-stubs" -#build --linkopt="-ld_classic" - build --features=debug_prefix_map_pwd_is_dot build --features=swift.cacheable_swiftmodules build --features=swift.debug_prefix_map diff --git a/.vscode/settings.json b/.vscode/settings.json index 2bb86d2b21..3090f4c8be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,5 @@ ".git/**": true }, "files.associations": { - "memory": "cpp" } } diff --git a/Telegram/BUILD b/Telegram/BUILD index 6ed4339a42..6cbd6e4e77 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1779,16 +1779,13 @@ ios_application( ":UrlTypesInfoPlist", ], deps = [ - #"//submodules/MtProtoKit", - #"//submodules/SSignalKit/SwiftSignalKit", - #"//submodules/Postbox", - #"//submodules/TelegramApi", - #"//submodules/TelegramCore", - #"//submodules/FFMpegBinding", - #"//submodules/Display", - #"//third-party/webrtc", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/FFMpegBinding", + "//third-party/webrtc", "//submodules/AsyncDisplayKit", "//submodules/ObjCRuntimeUtils", + ], ) diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/BUILD b/Tests/LottieMetalTest/SoftwareLottieRenderer/BUILD index 041c8eda84..9909820b33 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/BUILD +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/BUILD @@ -16,6 +16,11 @@ objc_library( "-Werror", "-I{}/Sources".format(package_name()), ], + cxxopts = [ + "-Werror", + "-std=c++17", + "-I{}/Sources".format(package_name()), + ], hdrs = glob([ "PublicHeaders/**/*.h", ]), diff --git a/build-system/bazel-utils/spm.bzl b/build-system/bazel-utils/spm.bzl index 7cd7038a13..4dd33b196a 100644 --- a/build-system/bazel-utils/spm.bzl +++ b/build-system/bazel-utils/spm.bzl @@ -54,10 +54,14 @@ _IGNORE_CC_LIBRARY_EMPTY_ATTRS = [ "include_prefix", "strip_include_prefix", "local_defines", + "conlyopts", + "module_interfaces", + "package_metadata", ] _CC_LIBRARY_ATTRS = { "copts": [], + "cxxopts": [], "defines": [], "deps": [], "hdrs": [], @@ -108,11 +112,11 @@ _IGNORE_OBJC_LIBRARY_EMPTY_ATTRS = [ "textual_hdrs", "sdk_includes", "conlyopts", - "cxxopts", ] _OBJC_LIBRARY_ATTRS = { "copts": [], + "cxxopts": [], "defines": [], "deps": [], "hdrs": [], @@ -351,6 +355,7 @@ def _collect_spm_modules_impl(target, ctx): "sources": sorted(sources + headers), "module_name": module_name, "copts": result_attrs["copts"], + "cxxopts": result_attrs["cxxopts"], "sdk_frameworks": result_attrs["sdk_frameworks"], "sdk_dylibs": result_attrs["sdk_dylibs"], "weak_sdk_frameworks": result_attrs["weak_sdk_frameworks"], @@ -366,6 +371,7 @@ def _collect_spm_modules_impl(target, ctx): "sources": sorted(sources + headers), "module_name": module_name, "copts": result_attrs["copts"], + "cxxopts": result_attrs["cxxopts"], "includes": result_attrs["includes"], } elif module_type == "swift_library": @@ -373,6 +379,7 @@ def _collect_spm_modules_impl(target, ctx): "name": result_attrs["name"], "type": module_type, "path": module_path, + "defines": result_attrs["defines"], "deps": dep_names, "sources": sorted(sources), "module_name": module_name, diff --git a/build-system/generate_spm.py b/build-system/generate_spm.py index fea5dbf31c..a6579dafbd 100644 --- a/build-system/generate_spm.py +++ b/build-system/generate_spm.py @@ -19,6 +19,24 @@ if os.path.exists(spm_files_dir): if not os.path.exists(spm_files_dir): os.makedirs(spm_files_dir) +def escape_swift_string_literal_component(text: str) -> str: + return text.replace('\\', '\\\\').replace('"', '\\"') + +parsed_modules = {} +for name, module in sorted(modules.items()): + is_empty = False + all_source_files = [] + for source in module.get("hdrs", []) + module["sources"]: + if source.endswith(('.a')): + continue + all_source_files.append(source) + if module["type"] == "objc_library" or module["type"] == "swift_library" or module["type"] == "cc_library": + if all_source_files == []: + is_empty = True + parsed_modules[name] = { + "is_empty": is_empty, + } + combined_lines = [] combined_lines.append("// swift-tools-version: 6.0") combined_lines.append("// The swift-tools-version declares the minimum version of Swift required to build this package.") @@ -28,11 +46,14 @@ combined_lines.append("") combined_lines.append("let package = Package(") combined_lines.append(" name: \"Telegram\",") combined_lines.append(" platforms: [") -combined_lines.append(" .iOS(.v13)") +combined_lines.append(" .iOS(.v12)") combined_lines.append(" ],") combined_lines.append(" products: [") for name, module in sorted(modules.items()): + if parsed_modules[name]["is_empty"]: + continue + if module["type"] == "objc_library" or module["type"] == "swift_library" or module["type"] == "cc_library": combined_lines.append(" .library(name: \"%s\", targets: [\"%s\"])," % (module["name"], module["name"])) @@ -40,6 +61,9 @@ combined_lines.append(" ],") combined_lines.append(" targets: [") for name, module in sorted(modules.items()): + if parsed_modules[name]["is_empty"]: + continue + module_type = module["type"] if module_type == "objc_library" or module_type == "cc_library" or module_type == "swift_library": combined_lines.append(" .target(") @@ -47,7 +71,7 @@ for name, module in sorted(modules.items()): linked_directory = None has_non_linked_sources = False - for source in module["sources"]: + for source in module["sources"] + module.get("hdrs", []): if source.startswith("bazel-out/"): linked_directory = "spm-files/" + name else: @@ -60,7 +84,8 @@ for name, module in sorted(modules.items()): combined_lines.append(" dependencies: [") for dep in module["deps"]: - combined_lines.append(" .target(name: \"%s\")," % dep) + if not parsed_modules[dep]["is_empty"]: + combined_lines.append(" .target(name: \"%s\")," % dep) combined_lines.append(" ],") if linked_directory: @@ -85,7 +110,7 @@ for name, module in sorted(modules.items()): continue # Check if any source file is under this directory has_source = False - for source in module["sources"]: + for source in module["sources"] + module.get("hdrs", []): rel_source = source[len(module["path"]) + 1:] if rel_source.startswith(dir_path + "/"): has_source = True @@ -98,16 +123,14 @@ for name, module in sorted(modules.items()): file_path = os.path.join(rel_path, f) if any(component.startswith('.') for component in file_path.split('/')): continue - if file_path not in [source[len(module["path"]) + 1:] for source in module["sources"]]: + if file_path not in [source[len(module["path"]) + 1:] for source in module["sources"] + module.get("hdrs", [])]: exclude_files_and_dirs.append(file_path) for item in exclude_files_and_dirs: combined_lines.append(" \"%s\"," % item) combined_lines.append(" ],") combined_lines.append(" sources: [") - for source in module["sources"]: - if source.endswith(('.h', '.hpp')): - continue + for source in module["sources"] + module.get("hdrs", []): linked_source_file_names = [] if not source.startswith(module["path"]): if source.startswith("bazel-out/"): @@ -138,7 +161,8 @@ for name, module in sorted(modules.items()): os.symlink(symlink_target, symlink_location) relative_source = source_file_name - combined_lines.append(" \"%s\"," % relative_source) + if not source.endswith(('.h', '.hpp', '.a')): + combined_lines.append(" \"%s\"," % relative_source) else: print("Source {} is not inside module path {}".format(source, module["path"])) sys.exit(1) @@ -152,16 +176,53 @@ for name, module in sorted(modules.items()): elif len(module["includes"]) == 1: combined_lines.append(" publicHeadersPath: \"%s\"," % module["includes"][0]) else: - print("Multiple includes are not supported yet: {}".format(module["includes"])) + print("{}: Multiple includes are not yet supported: {}".format(name, module["includes"])) sys.exit(1) - combined_lines.append(" cSettings: [") - combined_lines.append(" .unsafeFlags([") - for flag in module["copts"]: - # Escape C-string entities in flag - escaped_flag = flag.replace('\\', '\\\\').replace('"', '\\"') - combined_lines.append(" \"%s\"," % escaped_flag) - combined_lines.append(" ])") - combined_lines.append(" ],") + + defines = module.get("defines", []) + copts = module.get("copts", []) + cxxopts = module.get("cxxopts", []) + + if defines or copts: + combined_lines.append(" cSettings: [") + if defines: + for define in defines: + if "=" in define: + print("{}: Defines with = are not yet supported: {}".format(name, define)) + sys.exit(1) + else: + combined_lines.append(f' .define("{define}"),') + if copts: + combined_lines.append(" .unsafeFlags([") + for flag in copts: + escaped_flag = escape_swift_string_literal_component(flag) + combined_lines.append(f' "{escaped_flag}",') + combined_lines.append(" ])") + combined_lines.append(" ],") + + if defines or cxxopts: # Check for defines OR cxxopts + combined_lines.append(" cxxSettings: [") + if defines: # Add defines again if present, for C++ context + for define in defines: + if "=" in define: + print("{}: Defines with = are not yet supported: {}".format(name, define)) + sys.exit(1) + else: + combined_lines.append(f' .define("{define}"),') + if cxxopts: + combined_lines.append(" .unsafeFlags([") + for flag in cxxopts: + if flag.startswith("-std="): + if flag != "-std=c++17": + print("{}: Unsupported C++ standard: {}".format(name, flag)) + sys.exit(1) + else: + continue + escaped_flag = escape_swift_string_literal_component(flag) + combined_lines.append(f' "{escaped_flag}",') + combined_lines.append(" ])") + combined_lines.append(" ],") + combined_lines.append(" linkerSettings: [") if module_type == "objc_library": for framework in module["sdk_frameworks"]: @@ -171,12 +232,33 @@ for name, module in sorted(modules.items()): combined_lines.append(" ]") elif module_type == "swift_library": + defines = module.get("defines", []) + swift_copts = module.get("copts", []) # These are actual swiftc flags + + # Handle cSettings for defines if they exist + if defines: + combined_lines.append(" cSettings: [") + for define in defines: + combined_lines.append(f' .define("{define}"),') + combined_lines.append(" ],") + + # Handle swiftSettings combined_lines.append(" swiftSettings: [") combined_lines.append(" .swiftLanguageMode(.v5),") - combined_lines.append(" .unsafeFlags([") - for flag in module["copts"]: - combined_lines.append(" \"%s\"," % flag) - combined_lines.append(" ])") + # Add defines to swiftSettings as simple .define("STRING") flags + if defines: + for define in defines: + # For Swift settings, the define is passed as a single string, e.g., "KEY=VALUE" or "FLAG" + escaped_define = escape_swift_string_literal_component(define) # Escape the whole define string + combined_lines.append(f' .define("{escaped_define}"),') + + # Add copts (swiftc flags) to unsafeFlags in swiftSettings + if swift_copts: + combined_lines.append(" .unsafeFlags([") + for flag in swift_copts: + escaped_flag = escape_swift_string_literal_component(flag) + combined_lines.append(f' "{escaped_flag}",') + combined_lines.append(" ])") combined_lines.append(" ]") combined_lines.append(" ),") elif module["type"] == "root": @@ -185,7 +267,8 @@ for name, module in sorted(modules.items()): print("Unknown module type: {}".format(module["type"])) sys.exit(1) -combined_lines.append(" ]") +combined_lines.append(" ],") +combined_lines.append(" cxxLanguageStandard: .cxx17") combined_lines.append(")") combined_lines.append("") diff --git a/submodules/AsyncDisplayKit/BUILD b/submodules/AsyncDisplayKit/BUILD index 856b29e81d..f713d1184c 100644 --- a/submodules/AsyncDisplayKit/BUILD +++ b/submodules/AsyncDisplayKit/BUILD @@ -17,6 +17,10 @@ objc_library( copts = [ "-Werror", ], + cxxopts = [ + "-Werror", + "-std=c++17", + ], hdrs = public_headers, defines = [ "MINIMAL_ASDK", diff --git a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/AsyncDisplayKit.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/AsyncDisplayKit.h index dc00ed8339..265a9fe343 100644 --- a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/AsyncDisplayKit.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/AsyncDisplayKit.h @@ -63,3 +63,9 @@ #import #import + +#import +#import +#import +#import +#import diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 2389ead7a8..9f1f540f89 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1085,7 +1085,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }) strongSelf.present(controller) } - }, reportPeerIrrelevantGeoLocation: { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { [weak self] node, gesture in guard let strongSelf = self, let textInputPanelNode = strongSelf.textInputPanelNode else { diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 45340d8997..b99fe2b19b 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -137,7 +137,6 @@ public final class ChatPanelInterfaceInteraction { public let updateInputLanguage: (@escaping (String?) -> String?) -> Void public let unarchiveChat: () -> Void public let openLinkEditing: () -> Void - public let reportPeerIrrelevantGeoLocation: () -> Void public let displaySlowmodeTooltip: (UIView, CGRect) -> Void public let displaySendMessageOptions: (ASDisplayNode, ContextGesture) -> Void public let openScheduledMessages: () -> Void @@ -257,7 +256,6 @@ public final class ChatPanelInterfaceInteraction { updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, - reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (UIView, CGRect) -> Void, displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void, openScheduledMessages: @escaping () -> Void, @@ -376,7 +374,6 @@ public final class ChatPanelInterfaceInteraction { self.updateInputLanguage = updateInputLanguage self.unarchiveChat = unarchiveChat self.openLinkEditing = openLinkEditing - self.reportPeerIrrelevantGeoLocation = reportPeerIrrelevantGeoLocation self.displaySlowmodeTooltip = displaySlowmodeTooltip self.displaySendMessageOptions = displaySendMessageOptions self.openScheduledMessages = openScheduledMessages @@ -503,8 +500,8 @@ public final class ChatPanelInterfaceInteraction { }, requestStopPollInMessage: { _ in }, updateInputLanguage: { _ in }, unarchiveChat: { - }, openLinkEditing: openLinkEditing, reportPeerIrrelevantGeoLocation: { - }, displaySlowmodeTooltip: { _, _ in + }, openLinkEditing: openLinkEditing, + displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { }, openPeersNearby: { diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index 36a0986e16..43af0609e1 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -266,14 +266,12 @@ public final class ChatManagingBot: Equatable { public struct ChatContactStatus: Equatable { public var canAddContact: Bool - public var canReportIrrelevantLocation: Bool public var peerStatusSettings: PeerStatusSettings? public var invitedBy: Peer? public var managingBot: ChatManagingBot? - public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?, managingBot: ChatManagingBot?) { + public init(canAddContact: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?, managingBot: ChatManagingBot?) { self.canAddContact = canAddContact - self.canReportIrrelevantLocation = canReportIrrelevantLocation self.peerStatusSettings = peerStatusSettings self.invitedBy = invitedBy self.managingBot = managingBot @@ -286,9 +284,6 @@ public struct ChatContactStatus: Equatable { if !self.canAddContact { peerStatusSettings.flags.remove(.canAddContact) } - if !self.canReportIrrelevantLocation { - peerStatusSettings.flags.remove(.canReportIrrelevantGeoLocation) - } return peerStatusSettings.flags.isEmpty } @@ -296,9 +291,6 @@ public struct ChatContactStatus: Equatable { if lhs.canAddContact != rhs.canAddContact { return false } - if lhs.canReportIrrelevantLocation != rhs.canReportIrrelevantLocation { - return false - } if lhs.peerStatusSettings != rhs.peerStatusSettings { return false } @@ -454,6 +446,34 @@ public final class ChatPresentationInterfaceState: Equatable { case side } + public final class ReportReasonData: Equatable { + public let title: String + public let option: Data + public let message: String? + + public init(title: String, option: Data, message: String?) { + self.title = title + self.option = option + self.message = message + } + + public static func ==(lhs: ReportReasonData, rhs: ReportReasonData) -> Bool { + if lhs === rhs { + return true + } + if lhs.title != rhs.title { + return false + } + if lhs.option != rhs.option { + return false + } + if lhs.message != rhs.message { + return false + } + return true + } + } + public let interfaceState: ChatInterfaceState public let chatLocation: ChatLocation public let renderedPeer: RenderedPeer? @@ -505,7 +525,7 @@ public final class ChatPresentationInterfaceState: Equatable { public let activeGroupCallInfo: ChatActiveGroupCallInfo? public let hasActiveGroupCall: Bool public let importState: ChatPresentationImportState? - public let reportReason: (String, Data, String?)? + public let reportReason: ReportReasonData? public let showCommands: Bool public let hasBotCommands: Bool public let showSendAsPeers: Bool @@ -638,7 +658,7 @@ public final class ChatPresentationInterfaceState: Equatable { self.topicListDisplayMode = nil } - public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: UrlPreview?, editingUrlPreview: UrlPreview?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, historyFilter: HistoryFilter?, displayHistoryFilterAsList: Bool, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: (String, Data, String?)?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasAtLeast3Messages: 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, sendPaidMessageStars: StarsAmount?, acknowledgedPaidMessage: Bool, hasSavedChats: Bool, appliedBoosts: Int32?, boostsToUnrestrict: Int32?, businessIntro: TelegramBusinessIntro?, hasBirthdayToday: Bool, adMessage: Message?, peerVerification: PeerVerification?, starGiftsAvailable: Bool, alwaysShowGiftButton: Bool, disallowedGifts: TelegramDisallowedGifts?, topicListDisplayMode: TopicListDisplayMode?) { + public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: UrlPreview?, editingUrlPreview: UrlPreview?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, historyFilter: HistoryFilter?, displayHistoryFilterAsList: Bool, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReasonData?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasAtLeast3Messages: 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, sendPaidMessageStars: StarsAmount?, acknowledgedPaidMessage: Bool, hasSavedChats: Bool, appliedBoosts: Int32?, boostsToUnrestrict: Int32?, businessIntro: TelegramBusinessIntro?, hasBirthdayToday: Bool, adMessage: Message?, peerVerification: PeerVerification?, starGiftsAvailable: Bool, alwaysShowGiftButton: Bool, disallowedGifts: TelegramDisallowedGifts?, topicListDisplayMode: TopicListDisplayMode?) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer @@ -894,7 +914,7 @@ public final class ChatPresentationInterfaceState: Equatable { if lhs.importState != rhs.importState { return false } - if lhs.reportReason?.0 != rhs.reportReason?.0 || lhs.reportReason?.1 != rhs.reportReason?.1 || lhs.reportReason?.2 != rhs.reportReason?.2 { + if lhs.reportReason != rhs.reportReason { return false } if lhs.showCommands != rhs.showCommands { @@ -1207,7 +1227,7 @@ public final class ChatPresentationInterfaceState: Equatable { return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, 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, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, topicListDisplayMode: self.topicListDisplayMode) } - public func updatedReportReason(_ reportReason: (String, Data, String?)?) -> ChatPresentationInterfaceState { + public func updatedReportReason(_ reportReason: ReportReasonData?) -> 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, 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, hasAtLeast3Messages: self.hasAtLeast3Messages, 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, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, topicListDisplayMode: self.topicListDisplayMode) } @@ -1364,16 +1384,20 @@ public final class ChatPresentationInterfaceState: Equatable { } } -public func canBypassRestrictions(chatPresentationInterfaceState: ChatPresentationInterfaceState) -> Bool { - guard let boostsToUnrestrict = chatPresentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0 else { +public func canBypassRestrictions(boostsToUnrestrict: Int32?, appliedBoosts: Int32?) -> Bool { + guard let boostsToUnrestrict, boostsToUnrestrict > 0 else { return false } - if let appliedBoosts = chatPresentationInterfaceState.appliedBoosts, appliedBoosts >= boostsToUnrestrict { + if let appliedBoosts, appliedBoosts >= boostsToUnrestrict { return true } return false } +public func canBypassRestrictions(chatPresentationInterfaceState: ChatPresentationInterfaceState) -> Bool { + return canBypassRestrictions(boostsToUnrestrict: chatPresentationInterfaceState.boostsToUnrestrict, appliedBoosts: chatPresentationInterfaceState.appliedBoosts) +} + public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bool { if let peer = state.renderedPeer?.peer { let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: state) diff --git a/submodules/LottieCpp/BUILD b/submodules/LottieCpp/BUILD index 686f35669a..7569482041 100644 --- a/submodules/LottieCpp/BUILD +++ b/submodules/LottieCpp/BUILD @@ -22,6 +22,11 @@ objc_library( "-Werror", "-I{}/lottiecpp/Sources".format(package_name()), ], + cxxopts = [ + "-Werror", + "-std=c++17", + "-I{}/lottiecpp/Sources".format(package_name()), + ], hdrs = glob([ "lottiecpp/PublicHeaders/**/*.h", ]), @@ -49,6 +54,9 @@ cc_library( "PublicHeaders", ], copts = [], + cxxopts = [ + "-std=c++17", + ], visibility = ["//visibility:public"], linkstatic = 1, ) diff --git a/submodules/ShareItems/Impl/Sources/TGItemProviderSignals.m b/submodules/ShareItems/Impl/Sources/TGItemProviderSignals.m index 35f8ec5068..92aeb2c25e 100644 --- a/submodules/ShareItems/Impl/Sources/TGItemProviderSignals.m +++ b/submodules/ShareItems/Impl/Sources/TGItemProviderSignals.m @@ -46,7 +46,7 @@ } } - NSInteger providerIndex = -1; + __unused NSInteger providerIndex = -1; for (NSItemProvider *provider in providers) { providerIndex++; @@ -140,7 +140,7 @@ }]; } -static UIImage *TGScaleImageToPixelSize(UIImage *image, CGSize size) { +__unused static UIImage *TGScaleImageToPixelSize(UIImage *image, CGSize size) { UIGraphicsBeginImageContextWithOptions(size, true, 1.0f); [image drawInRect:CGRectMake(0, 0, size.width, size.height) blendMode:kCGBlendModeCopy alpha:1.0f]; UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); @@ -149,7 +149,7 @@ static UIImage *TGScaleImageToPixelSize(UIImage *image, CGSize size) { return result; } -static CGSize TGFitSize(CGSize size, CGSize maxSize) { +__unused static CGSize TGFitSize(CGSize size, CGSize maxSize) { if (size.width < 1) size.width = 1; if (size.height < 1) diff --git a/submodules/ShareItems/Impl/Sources/TGShareLocationSignals.m b/submodules/ShareItems/Impl/Sources/TGShareLocationSignals.m index 58387a1d92..f90521a186 100644 --- a/submodules/ShareItems/Impl/Sources/TGShareLocationSignals.m +++ b/submodules/ShareItems/Impl/Sources/TGShareLocationSignals.m @@ -64,12 +64,15 @@ NSString *const TGShareGoogleProvider = @"google"; NSString * TGURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ "; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" NSString *unescapedString = [string stringByReplacingPercentEscapesUsingEncoding:encoding]; if (unescapedString) { string = unescapedString; } return (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)); +#pragma clang diagnostic pop } @implementation TGQueryStringComponent diff --git a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift index 66ed923ab9..52ff5d11b2 100644 --- a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift @@ -23,9 +23,6 @@ extension PeerStatusSettings { if (flags & (1 << 4)) != 0 { result.insert(.addExceptionWhenAddingContact) } - if (flags & (1 << 5)) != 0 { - result.insert(.canReportIrrelevantGeoLocation) - } if (flags & (1 << 7)) != 0 { result.insert(.autoArchived) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift index e357982a70..1c2f485dff 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift @@ -13,7 +13,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { public static let canBlock = Flags(rawValue: 1 << 3) public static let canAddContact = Flags(rawValue: 1 << 4) public static let addExceptionWhenAddingContact = Flags(rawValue: 1 << 5) - public static let canReportIrrelevantGeoLocation = Flags(rawValue: 1 << 6) public static let autoArchived = Flags(rawValue: 1 << 7) public static let suggestAddMembers = Flags(rawValue: 1 << 8) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index 17c015060b..05a1a8dba1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -559,6 +559,7 @@ private class AdMessagesHistoryContextImpl { public class AdMessagesHistoryContext { private let queue = Queue() private let impl: QueueLocalObject + public let peerId: EnginePeer.Id public var state: Signal<(interPostInterval: Int32?, messages: [Message]), NoError> { return Signal { subscriber in @@ -576,6 +577,7 @@ public class AdMessagesHistoryContext { } public init(account: Account, peerId: PeerId) { + self.peerId = peerId let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { return AdMessagesHistoryContextImpl(queue: queue, account: account, peerId: peerId) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift index b87b061470..26605aff93 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift @@ -112,7 +112,6 @@ public enum EnginePeer: Equatable { public static let canBlock = Flags(rawValue: 1 << 3) public static let canAddContact = Flags(rawValue: 1 << 4) public static let addExceptionWhenAddingContact = Flags(rawValue: 1 << 5) - public static let canReportIrrelevantGeoLocation = Flags(rawValue: 1 << 6) public static let autoArchived = Flags(rawValue: 1 << 7) public static let suggestAddMembers = Flags(rawValue: 1 << 8) diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index ab6f27bced..9982a6d4cb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -134,7 +134,6 @@ public final class ChatRecentActionsController: TelegramBaseController { }, updateInputLanguage: { _ in }, unarchiveChat: { }, openLinkEditing: { - }, reportPeerIrrelevantGeoLocation: { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index f476b65173..5dfcc7fda1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -398,7 +398,6 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, updateInputLanguage: { _ in }, unarchiveChat: { }, openLinkEditing: { - }, reportPeerIrrelevantGeoLocation: { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index a58875b292..1c9c9817a2 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -682,7 +682,6 @@ final class PeerSelectionControllerNode: ASDisplayNode { }) strongSelf.present(controller, nil) } - }, reportPeerIrrelevantGeoLocation: { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { [weak self] node, gesture in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 22e1eb1b63..ff3a2ebd31 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -127,1728 +127,323 @@ import PostSuggestionsSettingsScreen import ChatSendStarsScreen extension ChatControllerImpl { - func reloadChatLocation(chatLocation: ChatLocation, isReady: Promise?) { - self._chatLocationInfoReady.set(.single(false)) - self.didSetChatLocationInfoReady = false + func reloadChatLocation(chatLocation: ChatLocation, chatLocationContextHolder: Atomic, historyNode: ChatHistoryListNodeImpl, isReady: Promise?) { + self.contentDataReady.set(false) - if let peerId = chatLocation.peerId, peerId != self.context.account.peerId { - switch subject { - case .pinnedMessages, .scheduledMessages, .messageOptions: - break - default: - self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId, threadId: chatLocation.threadId) - } - } + self.contentDataDisposable?.dispose() - let context = self.context - let chatLocationPeerId: PeerId? = chatLocation.peerId - let mode = self.mode - let subject = self.subject - let peerId = chatLocationPeerId - - switch chatLocation { - case .peer: - self.chatLocationInfoData = .peer(Promise()) - case let .replyThread(replyThreadMessage): - let promise = Promise() - if let effectiveMessageId = replyThreadMessage.effectiveMessageId { - promise.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: effectiveMessageId)) - |> map { message -> Message? in - guard let message = message else { - return nil - } - return message._asMessage() - }) - } else { - promise.set(.single(nil)) - } - self.chatLocationInfoData = .replyThread(promise) - case .customChatContents: - self.chatLocationInfoData = .customChatContents - } - - let managingBot: Signal - if let peerId = chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser { - managingBot = self.context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Peer.ChatManagingBot(id: peerId) + let configuration: Signal = self.presentationInterfaceStatePromise.get() + |> map { presentationInterfaceState -> ChatControllerImpl.ContentData.Configuration in + return ChatControllerImpl.ContentData.Configuration( + subject: presentationInterfaceState.subject, + selectionState: presentationInterfaceState.interfaceState.selectionState, + reportReason: presentationInterfaceState.reportReason ) - |> mapToSignal { result -> Signal in - guard let result else { - return .single(nil) - } - return context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Peer.Peer(id: result.id) - ) - |> map { botPeer -> ChatManagingBot? in - guard let botPeer else { - return nil - } - - return ChatManagingBot(bot: botPeer, isPaused: result.isPaused, canReply: result.canReply, settingsUrl: result.manageUrl) - } - } - |> distinctUntilChanged - } else { - managingBot = .single(nil) } + |> distinctUntilChanged - if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId { - peerView.set(context.account.viewTracker.peerView(peerId)) - var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil)) - var hasScheduledMessages: Signal = .single(false) - - if peerId.namespace == Namespaces.Peer.CloudChannel { - let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView.get() - |> map { view -> Bool? in - if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { - if case .broadcast = peer.info { - return nil - } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { - return true - } else { - return false - } - } else { - return false - } - } - |> distinctUntilChanged - |> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in - if let isLarge = isLarge { - if isLarge { - return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) - |> map { value -> (total: Int32?, recent: Int32?) in - return (nil, value) - } - } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map { value -> (total: Int32?, recent: Int32?) in - return (value.total, value.recent) - } - } - } else { - return .single((nil, nil)) - } - } - onlineMemberCount = recentOnlineSignal - - self.reportIrrelvantGeoNoticePromise.set(context.engine.data.get(TelegramEngine.EngineData.Item.Notices.Notice(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId))) - |> map { entry -> Bool? in - if let _ = entry?.get(ApplicationSpecificBoolNotice.self) { - return true - } else { - return false - } - }) - } else { - self.reportIrrelvantGeoNoticePromise.set(.single(nil)) - } - - var isScheduledOrPinnedMessages = false - switch self.subject { - case .scheduledMessages, .pinnedMessages, .messageOptions: - isScheduledOrPinnedMessages = true - default: - break - } - - if chatLocation.peerId != nil, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat { - let chatLocationContextHolder = self.chatLocationContextHolder - hasScheduledMessages = peerView.get() - |> take(1) - |> mapToSignal { view -> Signal in - 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 - } - } - } - } - - var displayedCountSignal: Signal = .single(nil) - var subtitleTextSignal: Signal = .single(nil) - if case .pinnedMessages = subject { - displayedCountSignal = self.topPinnedMessageSignal(latest: true) - |> map { message -> Int? in - return message?.totalCount - } - |> distinctUntilChanged - } else if case let .messageOptions(peerIds, messageIds, info) = subject { - displayedCountSignal = self.presentationInterfaceStatePromise.get() - |> map { state -> Int? in - if let selectionState = state.interfaceState.selectionState { - return selectionState.selectedIds.count - } else { - return messageIds.count - } - } - |> distinctUntilChanged - - let peers = self.context.account.postbox.multiplePeersView(peerIds) - |> take(1) - - let presentationData = self.presentationData - - switch info { - case let .forward(forward): - subtitleTextSignal = combineLatest(peers, forward.options, displayedCountSignal) - |> map { peersView, options, count in - let peers = peersView.peers.values - if !peers.isEmpty { - if peers.count == 1, let peer = peers.first { - if let peer = peer as? TelegramUser { - let displayName = EnginePeer(peer).compactDisplayTitle - if count == 1 { - if options.hideNames { - return presentationData.strings.Conversation_ForwardOptions_UserMessageForwardHidden(displayName).string - } else { - return presentationData.strings.Conversation_ForwardOptions_UserMessageForwardVisible(displayName).string - } - } else { - if options.hideNames { - return presentationData.strings.Conversation_ForwardOptions_UserMessagesForwardHidden(displayName).string - } else { - return presentationData.strings.Conversation_ForwardOptions_UserMessagesForwardVisible(displayName).string - } - } - } else if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - if count == 1 { - if options.hideNames { - return presentationData.strings.Conversation_ForwardOptions_ChannelMessageForwardHidden - } else { - return presentationData.strings.Conversation_ForwardOptions_ChannelMessageForwardVisible - } - } else { - if options.hideNames { - return presentationData.strings.Conversation_ForwardOptions_ChannelMessagesForwardHidden - } else { - return presentationData.strings.Conversation_ForwardOptions_ChannelMessagesForwardVisible - } - } - } else { - if count == 1 { - if options.hideNames { - return presentationData.strings.Conversation_ForwardOptions_GroupMessageForwardHidden - } else { - return presentationData.strings.Conversation_ForwardOptions_GroupMessageForwardVisible - } - } else { - if options.hideNames { - return presentationData.strings.Conversation_ForwardOptions_GroupMessagesForwardHidden - } else { - return presentationData.strings.Conversation_ForwardOptions_GroupMessagesForwardVisible - } - } - } - } else { - if count == 1 { - if options.hideNames { - return presentationData.strings.Conversation_ForwardOptions_RecipientsMessageForwardHidden - } else { - return presentationData.strings.Conversation_ForwardOptions_RecipientsMessageForwardVisible - } - } else { - if options.hideNames { - return presentationData.strings.Conversation_ForwardOptions_RecipientsMessagesForwardHidden - } else { - return presentationData.strings.Conversation_ForwardOptions_RecipientsMessagesForwardVisible - } - } - } - } else { - return nil - } - } - case let .reply(reply): - subtitleTextSignal = reply.selectionState.get() - |> map { selectionState -> String? in - if !selectionState.canQuote { - return nil - } - return presentationData.strings.Chat_SubtitleQuoteSelectionTip - } - case let .link(link): - subtitleTextSignal = link.options - |> map { options -> String? in - if options.hasAlternativeLinks { - return presentationData.strings.Chat_SubtitleLinkListTip - } else { - return nil - } - } - |> distinctUntilChanged - } - } - - let hasPeerInfo: Signal - if peerId == context.account.peerId { - hasPeerInfo = .single(true) - |> then( - hasAvailablePeerInfoMediaPanes(context: context, peerId: peerId) - ) - } else { - hasPeerInfo = .single(true) - } - - enum MessageOptionsTitleInfo { - case reply(hasQuote: Bool) - } - let messageOptionsTitleInfo: Signal - if case let .messageOptions(_, _, info) = self.subject { - switch info { - case .forward, .link: - messageOptionsTitleInfo = .single(nil) - case let .reply(reply): - messageOptionsTitleInfo = reply.selectionState.get() - |> map { selectionState -> Bool in - return selectionState.quote != nil - } - |> distinctUntilChanged - |> map { hasQuote -> MessageOptionsTitleInfo in - return .reply(hasQuote: hasQuote) - } - } - } else { - messageOptionsTitleInfo = .single(nil) - } - - var isFirstTimeValue = true - self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get(), hasPeerInfo, messageOptionsTitleInfo) - |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState, hasPeerInfo, messageOptionsTitleInfo in - if let strongSelf = self { - let isFirstTime = isFirstTimeValue - isFirstTimeValue = false - - var isScheduledMessages = false - if case .scheduledMessages = presentationInterfaceState.subject { - isScheduledMessages = true - } - - if case let .messageOptions(_, _, info) = presentationInterfaceState.subject { - if case .reply = info { - let titleContent: ChatTitleContent - if case let .reply(hasQuote) = messageOptionsTitleInfo, hasQuote { - titleContent = .custom(presentationInterfaceState.strings.Chat_TitleQuoteSelection, subtitleText, false) - } else { - titleContent = .custom(presentationInterfaceState.strings.Chat_TitleReply, subtitleText, false) - } - if strongSelf.chatTitleView?.titleContent != titleContent { - if strongSelf.chatTitleView?.titleContent != nil { - strongSelf.chatTitleView?.animateLayoutTransition() - } - strongSelf.chatTitleView?.titleContent = titleContent - } - } else if case .link = info { - strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitleLinkOptions, subtitleText, false) - } else if displayedCount == 1 { - strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false) - } else { - strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1)), subtitleText, false) - } - } else if let selectionState = presentationInterfaceState.interfaceState.selectionState { - if selectionState.selectedIds.count > 0 { - strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count)), nil, false) - } else { - if let (title, _, _) = presentationInterfaceState.reportReason { - strongSelf.chatTitleView?.titleContent = .custom(title, presentationInterfaceState.strings.Conversation_SelectMessages, false) - } else { - strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_SelectMessages, nil, false) - } - } - } else if let peer = peerViewMainPeer(peerView) { - if case .pinnedMessages = presentationInterfaceState.subject { - strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) - } else if let channel = peer as? TelegramChannel, channel.isMonoForum { - if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { - //TODO:localize - strongSelf.chatTitleView?.titleContent = .custom("\(mainPeer.debugDisplayTitle) Messages", nil, false) - } else { - strongSelf.chatTitleView?.titleContent = .custom(channel.debugDisplayTitle, nil, false) - } - } else { - strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) - let imageOverride: AvatarNodeImageOverride? - if strongSelf.context.account.peerId == peer.id { - imageOverride = .savedMessagesIcon - } else if peer.id.isReplies { - imageOverride = .repliesIcon - } else if peer.id.isAnonymousSavedMessages { - imageOverride = .anonymousSavedMessagesIcon(isColored: true) - } else if peer.isDeleted { - imageOverride = .deletedIcon - } else { - imageOverride = nil - } - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride, synchronousLoad: isFirstTime) - if case .standard(.previewing) = strongSelf.mode { - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false - } else { - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil - } - strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = presentationInterfaceState.strings.Conversation_ContextMenuOpenProfile - - strongSelf.storyStats = peerView.storyStats - if let avatarNode = strongSelf.avatarNode { - avatarNode.avatarNode.setStoryStats(storyStats: peerView.storyStats.flatMap { storyStats -> AvatarNode.StoryStats? in - if storyStats.totalCount == 0 { - return nil - } - if storyStats.unseenCount == 0 { - return nil - } - return AvatarNode.StoryStats( - totalCount: storyStats.totalCount, - unseenCount: storyStats.unseenCount, - hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends - ) - }, presentationParams: AvatarNode.StoryPresentationParams( - colors: AvatarNode.Colors(theme: strongSelf.presentationData.theme), - lineWidth: 1.5, - inactiveLineWidth: 1.5 - ), transition: .immediate) - } - } - } - } - })) - - let threadInfo: Signal - if let threadId = chatLocation.threadId { - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) - threadInfo = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> EngineMessageHistoryThread.Info? in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return nil - } - guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { - return nil - } - return data.info - } - |> distinctUntilChanged - } else { - threadInfo = .single(nil) - } - - let hasSearchTags: Signal - if let peerId = chatLocation.peerId, peerId == context.account.peerId { - hasSearchTags = context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId) - ) - |> map { tags -> Bool in - return !tags.isEmpty - } - |> distinctUntilChanged - } else { - hasSearchTags = .single(false) - } - - let hasSavedChats: Signal - if case .peer(context.account.peerId) = chatLocation { - hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved() - } else { - hasSavedChats = .single(false) - } - - let isPremiumRequiredForMessaging: Signal - if let peerId = chatLocation.peerId { - isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId) - |> distinctUntilChanged - } else { - isPremiumRequiredForMessaging = .single(false) - } - - let adMessage: Signal - if let adMessagesContext = self.chatDisplayNode.historyNode.adMessagesContext { - adMessage = adMessagesContext.state |> map { $0.messages.first } - } else { - adMessage = .single(nil) - } - - let displayedPeerVerification: Signal - if let peerId = chatLocation.peerId { - displayedPeerVerification = ApplicationSpecificNotice.displayedPeerVerification(accountManager: context.sharedContext.accountManager, peerId: peerId) - |> take(1) - } else { - displayedPeerVerification = .single(false) - } - - let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy()) - - self.peerDisposable.set(combineLatest( - queue: Queue.mainQueue(), - peerView.get(), - context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()), - onlineMemberCount, - hasScheduledMessages, - self.reportIrrelvantGeoNoticePromise.get(), - displayedCountSignal, - threadInfo, - hasSearchTags, - hasSavedChats, - isPremiumRequiredForMessaging, - managingBot, - adMessage, - displayedPeerVerification, - globalPrivacySettings - ).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, adMessage, displayedPeerVerification, globalPrivacySettings in - if let strongSelf = self { - if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging && managingBot == strongSelf.presentationInterfaceState.contactStatus?.managingBot && adMessage?.id == strongSelf.presentationInterfaceState.adMessage?.id { - return - } - - strongSelf.reportIrrelvantGeoNotice = peerReportNotice - strongSelf.hasScheduledMessages = hasScheduledMessages - - var upgradedToPeerId: PeerId? - var movedToForumTopics = false - if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { - upgradedToPeerId = migrationReference.peerId - } - if let previous = strongSelf.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, !channel.isForumOrMonoForum, let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.isForumOrMonoForum { - movedToForumTopics = true - } - - var shouldDismiss = false - if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.membership != .Removed, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, updatedGroup.membership == .Removed { - shouldDismiss = true - } else if let previous = strongSelf.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, channel.participationStatus != .kicked, let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.participationStatus == .kicked { - shouldDismiss = true - } else if let previous = strongSelf.peerView, let secretChat = previous.peers[previous.peerId] as? TelegramSecretChat, case .active = secretChat.embeddedState, let updatedSecretChat = peerView.peers[peerView.peerId] as? TelegramSecretChat, case .terminated = updatedSecretChat.embeddedState { - shouldDismiss = true - } - - var wasGroupChannel: Bool? - if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - wasGroupChannel = true - } else { - wasGroupChannel = false - } - } - var isGroupChannel: Bool? - if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - isGroupChannel = true - } else { - isGroupChannel = false - } - } - let firstTime = strongSelf.peerView == nil - strongSelf.peerView = peerView - strongSelf.threadInfo = threadInfo - if wasGroupChannel != isGroupChannel { - if let isGroupChannel = isGroupChannel, isGroupChannel { - let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(engine: strongSelf.context.engine, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(engine: strongSelf.context.engine, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let disposable = DisposableSet() - disposable.add(recentDisposable) - disposable.add(adminsDisposable) - strongSelf.chatAdditionalDataDisposable.set(disposable) - } else { - strongSelf.chatAdditionalDataDisposable.set(nil) - } - } - if strongSelf.isNodeLoaded { - strongSelf.chatDisplayNode.overlayTitle = strongSelf.overlayTitle - } - var peerIsMuted = false - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - peerIsMuted = true - } else if case .default = notificationSettings.muteState { - if let peer = peerView.peers[peerView.peerId] { - if peer is TelegramUser { - peerIsMuted = !globalNotificationSettings.privateChats.enabled - } else if peer is TelegramGroup { - peerIsMuted = !globalNotificationSettings.groupChats.enabled - } else if let channel = peer as? TelegramChannel { - switch channel.info { - case .group: - peerIsMuted = !globalNotificationSettings.groupChats.enabled - case .broadcast: - peerIsMuted = !globalNotificationSettings.channels.enabled - } - } - } - } - } - var starGiftsAvailable = false - var peerDiscussionId: PeerId? - var peerGeoLocation: PeerGeoLocation? - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { - if case .broadcast = peer.info { - starGiftsAvailable = cachedData.flags.contains(.starGiftsAvailable) - } else { - peerGeoLocation = cachedData.peerGeoLocation - } - if case let .known(value) = cachedData.linkedDiscussionPeerId { - peerDiscussionId = value - } - } - var renderedPeer: RenderedPeer? - var contactStatus: ChatContactStatus? - var businessIntro: TelegramBusinessIntro? - var sendPaidMessageStars: StarsAmount? - var alwaysShowGiftButton = false - var disallowedGifts: TelegramDisallowedGifts? - if let peer = peerView.peers[peerView.peerId] { - if let cachedData = peerView.cachedData as? CachedUserData { - contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot) - if case let .known(value) = cachedData.businessIntro { - businessIntro = value - } - if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { - } else { - sendPaidMessageStars = cachedData.sendPaidMessageStars - if cachedData.disallowedGifts != .All { - alwaysShowGiftButton = globalPrivacySettings.displayGiftButton || cachedData.flags.contains(.displayGiftButton) - } - disallowedGifts = cachedData.disallowedGifts - } - } else if let cachedData = peerView.cachedData as? CachedGroupData { - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) - } else if let cachedData = peerView.cachedData as? CachedChannelData { - var canReportIrrelevantLocation = true - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { - canReportIrrelevantLocation = false - } - if let peerReportNotice = peerReportNotice, peerReportNotice { - canReportIrrelevantLocation = false - } - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) - - if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { - if channel.flags.contains(.isCreator) || channel.adminRights != nil { - } else { - sendPaidMessageStars = channel.sendPaidMessageStars - } - } - } - - var peers = SimpleDictionary() - peers[peer.id] = peer - if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { - peers[associatedPeer.id] = associatedPeer - } - renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media) - } - - var isNotAccessible: Bool = false - if let cachedChannelData = peerView.cachedData as? CachedChannelData { - isNotAccessible = cachedChannelData.isNotAccessible - } - - if firstTime && isNotAccessible { - strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) - } - - var hasBots: Bool = false - var hasBotCommands: Bool = false - var botMenuButton: BotMenuButton = .commands - var currentSendAsPeerId: PeerId? - var autoremoveTimeout: Int32? - var copyProtectionEnabled: Bool = false - var hasBirthdayToday = false - var peerVerification: PeerVerification? - if let peer = peerView.peers[peerView.peerId] { - if !displayedPeerVerification { - if let cachedUserData = peerView.cachedData as? CachedUserData { - peerVerification = cachedUserData.verification - } else if let cachedChannelData = peerView.cachedData as? CachedChannelData { - peerVerification = cachedChannelData.verification - } - } - copyProtectionEnabled = peer.isCopyProtectionEnabled - if let cachedGroupData = peerView.cachedData as? CachedGroupData { - if !cachedGroupData.botInfos.isEmpty { - hasBots = true - } - let botCommands = cachedGroupData.botInfos.reduce(into: [], { result, info in - result.append(contentsOf: info.botInfo.commands) - }) - if !botCommands.isEmpty { - hasBotCommands = true - } - if case let .known(value) = cachedGroupData.autoremoveTimeout { - autoremoveTimeout = value?.effectiveValue - } - } else if let cachedChannelData = peerView.cachedData as? CachedChannelData { - if let channel = peer as? TelegramChannel, channel.isMonoForum { - if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { - currentSendAsPeerId = channel.linkedMonoforumId - } else { - currentSendAsPeerId = nil - } - } else { - currentSendAsPeerId = cachedChannelData.sendAsPeerId - if let channel = peer as? TelegramChannel, case .group = channel.info { - if !cachedChannelData.botInfos.isEmpty { - hasBots = true - } - let botCommands = cachedChannelData.botInfos.reduce(into: [], { result, info in - result.append(contentsOf: info.botInfo.commands) - }) - if !botCommands.isEmpty { - hasBotCommands = true - } - } - } - if case let .known(value) = cachedChannelData.autoremoveTimeout { - autoremoveTimeout = value?.effectiveValue - } - } else if let cachedUserData = peerView.cachedData as? CachedUserData { - botMenuButton = cachedUserData.botInfo?.menuButton ?? .commands - if case let .known(value) = cachedUserData.autoremoveTimeout { - autoremoveTimeout = value?.effectiveValue - } - if let botInfo = cachedUserData.botInfo, !botInfo.commands.isEmpty { - hasBotCommands = true - } - if let birthday = cachedUserData.birthday { - let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date()) - if today.day == Int(birthday.day) && today.month == Int(birthday.month) { - hasBirthdayToday = true - } - } - } - } - - let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive - - var explicitelyCanPinMessages: Bool = false - if let cachedUserData = peerView.cachedData as? CachedUserData { - explicitelyCanPinMessages = cachedUserData.canPinMessages - } else if peerView.peerId == context.account.peerId { - explicitelyCanPinMessages = true - } - - var animated = false - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { - animated = true - } - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { - if peer.participationStatus != updated.participationStatus { - animated = true - } - } - - var didDisplayActionsPanel = false - if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - didDisplayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.suggestAddMembers) { - didDisplayActionsPanel = true - } - } - } - if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil { - didDisplayActionsPanel = true - } - if strongSelf.presentationInterfaceState.search != nil && strongSelf.presentationInterfaceState.hasSearchTags { - didDisplayActionsPanel = true - } - - var displayActionsPanel = false - if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.suggestAddMembers) { - displayActionsPanel = true - } - } - } - if let contactStatus, contactStatus.managingBot != nil { - displayActionsPanel = true - } - if strongSelf.presentationInterfaceState.search != nil && hasSearchTags { - displayActionsPanel = true - } - - if displayActionsPanel != didDisplayActionsPanel { - animated = true - } - - if strongSelf.preloadHistoryPeerId != peerDiscussionId { - strongSelf.preloadHistoryPeerId = peerDiscussionId - if let peerDiscussionId = peerDiscussionId, let channel = peerView.peers[peerView.peerId] as? TelegramChannel, case .broadcast = channel.info { - let combinedDisposable = DisposableSet() - strongSelf.preloadHistoryPeerIdDisposable.set(combinedDisposable) - combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: peerDiscussionId).startStrict()) - combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) - } else { - strongSelf.preloadHistoryPeerIdDisposable.set(nil) - } - } - - var appliedBoosts: Int32? - var boostsToUnrestrict: Int32? - if let cachedChannelData = peerView.cachedData as? CachedChannelData { - appliedBoosts = cachedChannelData.appliedBoosts - boostsToUnrestrict = cachedChannelData.boostsToUnrestrict - } - - if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId { - strongSelf.premiumOrStarsRequiredDisposable = ((strongSelf.context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone() - } - - var adMessage = adMessage - if let peer = peerView.peers[peerView.peerId] as? TelegramUser, peer.botInfo != nil { - } else { - adMessage = nil - } - - if strongSelf.presentationInterfaceState.adMessage?.id != adMessage?.id { - animated = true - } - - strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { - return $0.updatedPeer { _ in - return renderedPeer - }.updatedIsNotAccessible(isNotAccessible) - .updatedContactStatus(contactStatus) - .updatedHasBots(hasBots) - .updatedHasBotCommands(hasBotCommands) - .updatedBotMenuButton(botMenuButton) - .updatedIsArchived(isArchived) - .updatedPeerIsMuted(peerIsMuted) - .updatedPeerDiscussionId(peerDiscussionId) - .updatedPeerGeoLocation(peerGeoLocation) - .updatedExplicitelyCanPinMessages(explicitelyCanPinMessages) - .updatedHasScheduledMessages(hasScheduledMessages) - .updatedAutoremoveTimeout(autoremoveTimeout) - .updatedCurrentSendAsPeerId(currentSendAsPeerId) - .updatedCopyProtectionEnabled(copyProtectionEnabled) - .updatedHasSearchTags(hasSearchTags) - .updatedIsPremiumRequiredForMessaging(isPremiumRequiredForMessaging) - .updatedSendPaidMessageStars(sendPaidMessageStars) - .updatedAlwaysShowGiftButton(alwaysShowGiftButton) - .updatedDisallowedGifts(disallowedGifts) - .updatedHasSavedChats(hasSavedChats) - .updatedAppliedBoosts(appliedBoosts) - .updatedBoostsToUnrestrict(boostsToUnrestrict) - .updatedHasBirthdayToday(hasBirthdayToday) - .updatedBusinessIntro(businessIntro) - .updatedAdMessage(adMessage) - .updatedPeerVerification(peerVerification) - .updatedStarGiftsAvailable(starGiftsAvailable) - .updatedInterfaceState { interfaceState in - var interfaceState = interfaceState - - if let channel = renderedPeer?.peer as? TelegramChannel { - if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } else if channel.hasBannedPermission(.banSendVoice) != nil { - if channel.hasBannedPermission(.banSendInstantVideos) == nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) - } - } else if channel.hasBannedPermission(.banSendInstantVideos) != nil { - if channel.hasBannedPermission(.banSendVoice) == nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } - } - } else if let group = renderedPeer?.peer as? TelegramGroup { - if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } else if group.hasBannedPermission(.banSendVoice) { - if !group.hasBannedPermission(.banSendInstantVideos) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) - } - } else if group.hasBannedPermission(.banSendInstantVideos) { - if !group.hasBannedPermission(.banSendVoice) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } - } - } - - return interfaceState - } - }) - - if case .standard(.default) = mode, let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info { - var isRegularChat = false - if let subject = subject { - if case .message = subject { - isRegularChat = true - } - } else { - isRegularChat = true - } - if strongSelf.nextChannelToReadDisposable == nil, let peerId = chatLocation.peerId, let customChatNavigationStack = strongSelf.customChatNavigationStack { - if let index = customChatNavigationStack.firstIndex(of: peerId), index != customChatNavigationStack.count - 1 { - let nextPeerId = customChatNavigationStack[index + 1] - strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(), - strongSelf.context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Peer.Peer(id: nextPeerId) - ), - ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager) - ) - |> then(.complete() |> delay(1.0, queue: .mainQueue())) - |> restart).startStrict(next: { nextPeer, nextChatSuggestionTip in - guard let strongSelf = self else { - return - } - - strongSelf.offerNextChannelToRead = true - strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in - return (peer: nextPeer, threadData: nil, unreadCount: 0, location: .same) - } - strongSelf.chatDisplayNode.historyNode.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 - - let nextPeerId = nextPeer?.id - - if strongSelf.preloadNextChatPeerId != nextPeerId { - strongSelf.preloadNextChatPeerId = nextPeerId - if let nextPeerId = nextPeerId { - let combinedDisposable = DisposableSet() - strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable) - combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: nextPeerId).startStrict()) - combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId)) - } else { - strongSelf.preloadNextChatPeerIdDisposable.set(nil) - } - } - - strongSelf.updateNextChannelToReadVisibility() - }) - } - } else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil { - //TODO:loc optimize - let accountPeerId = strongSelf.context.account.peerId - strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(), - strongSelf.context.engine.peers.getNextUnreadChannel(peerId: channel.id, chatListFilterId: strongSelf.currentChatListFilter, getFilterPredicate: { data in - return chatListFilterPredicate(filter: data, accountPeerId: accountPeerId) - }), - ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager) - ) - |> then(.complete() |> delay(1.0, queue: .mainQueue())) - |> restart).startStrict(next: { nextPeer, nextChatSuggestionTip in - guard let strongSelf = self else { - return - } - - strongSelf.offerNextChannelToRead = true - strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in - return (peer: nextPeer.peer, threadData: nil, unreadCount: nextPeer.unreadCount, location: nextPeer.location) - } - strongSelf.chatDisplayNode.historyNode.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 - - let nextPeerId = nextPeer?.peer.id - - if strongSelf.preloadNextChatPeerId != nextPeerId { - strongSelf.preloadNextChatPeerId = nextPeerId - if let nextPeerId = nextPeerId { - let combinedDisposable = DisposableSet() - strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable) - combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: nextPeerId).startStrict()) - combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId)) - } else { - strongSelf.preloadNextChatPeerIdDisposable.set(nil) - } - } - - strongSelf.updateNextChannelToReadVisibility() - }) - } - } - - if !strongSelf.didSetChatLocationInfoReady { - strongSelf.didSetChatLocationInfoReady = true - strongSelf._chatLocationInfoReady.set(.single(true)) - } - isReady?.set(.single(true)) - strongSelf.updateReminderActivity() - if let upgradedToPeerId = upgradedToPeerId { - if let navigationController = strongSelf.effectiveNavigationController { - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === strongSelf }) { - viewControllers[index] = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(id: upgradedToPeerId)) - navigationController.setViewControllers(viewControllers, animated: false) - } - } - } else if movedToForumTopics { - if let navigationController = strongSelf.effectiveNavigationController { - let chatListController = strongSelf.context.sharedContext.makeChatListController(context: strongSelf.context, location: .forum(peerId: peerView.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) - navigationController.replaceController(strongSelf, with: chatListController, animated: true) - } - } else if shouldDismiss { - strongSelf.dismiss() - } - } - })) - - if peerId == context.account.peerId { - self.preloadSavedMessagesChatsDisposable = context.engine.messages.savedMessagesPeerListHead().start() - } - } else if case let .replyThread(messagePromise) = self.chatLocationInfoData, let peerId = peerId { - self.reportIrrelvantGeoNoticePromise.set(.single(nil)) - - let replyThreadType: ChatTitleContent.ReplyThreadType - var replyThreadId: Int64? - switch chatLocation { - case .peer: - replyThreadType = .replies - case let .replyThread(replyThreadMessage): - if replyThreadMessage.peerId == context.account.peerId { - replyThreadId = replyThreadMessage.threadId - replyThreadType = .replies - } else { - replyThreadId = replyThreadMessage.threadId - if replyThreadMessage.isChannelPost { - replyThreadType = .comments - } else { - replyThreadType = .replies - } - } - case .customChatContents: - replyThreadType = .replies - } - - let peerView = context.account.viewTracker.peerView(peerId) - - let messageAndTopic = messagePromise.get() - |> mapToSignal { message -> Signal<(message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int), NoError> in - guard let replyThreadId = replyThreadId else { - return .single((message, nil, 0)) - } - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: replyThreadId) - let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Cloud, customTag: nil) - let localCountViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Local, customTag: nil) - return context.account.postbox.combinedView(keys: [viewKey, countViewKey, localCountViewKey]) - |> map { views -> (message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int) in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return (message, nil, 0) - } - var messageCount = 0 - if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { - if replyThreadId == 1 { - messageCount += Int(count) - } else { - messageCount += max(Int(count) - 1, 0) - } - } - if let summaryView = views.views[localCountViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { - messageCount += Int(count) - } - return (message, view.info?.data.get(MessageHistoryThreadData.self), messageCount) - } - } - - let savedMessagesPeerId: PeerId? - if case let .replyThread(replyThreadMessage) = chatLocation, (replyThreadMessage.peerId == context.account.peerId || replyThreadMessage.isMonoforumPost) { - savedMessagesPeerId = PeerId(replyThreadMessage.threadId) - } else { - savedMessagesPeerId = nil - } - - let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)?, NoError> - if let savedMessagesPeerId { - let threadPeerId = savedMessagesPeerId - let basicPeerKey: PostboxViewKey = .peer(peerId: threadPeerId, components: []) - let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: savedMessagesPeerId.toInt64(), namespace: Namespaces.Message.Cloud, customTag: nil) - savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey]) - |> map { views -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)? in - var peer: EnginePeer? - var presence: EnginePeer.Presence? - if let peerView = views.views[basicPeerKey] as? PeerView { - peer = peerViewMainPeer(peerView).flatMap(EnginePeer.init) - presence = peerView.peerPresences[threadPeerId].flatMap(EnginePeer.Presence.init) - } - - var messageCount = 0 - if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { - messageCount += Int(count) - } - - return (peer, messageCount, presence) - } - |> distinctUntilChanged(isEqual: { lhs, rhs in - if lhs?.peer != rhs?.peer { - return false - } - if lhs?.messageCount != rhs?.messageCount { - return false - } - if lhs?.presence != rhs?.presence { - return false - } - return true - }) - } else { - savedMessagesPeer = .single(nil) - } - - var isScheduledOrPinnedMessages = false - switch subject { - case .scheduledMessages, .pinnedMessages, .messageOptions: - isScheduledOrPinnedMessages = true - default: - break - } - - var hasScheduledMessages: Signal = .single(false) - if chatLocation.peerId != nil, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat { - let chatLocationContextHolder = self.chatLocationContextHolder - hasScheduledMessages = peerView - |> take(1) - |> mapToSignal { view -> Signal in - if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) { - return .single(false) - } else { - 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 - } - } - } - } - - var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil)) - if peerId.namespace == Namespaces.Peer.CloudChannel { - let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView - |> map { view -> Bool? in - if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { - if case .broadcast = peer.info { - return nil - } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { - return true - } else { - return false - } - } else { - return false - } - } - |> distinctUntilChanged - |> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in - if let isLarge = isLarge { - if isLarge { - return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) - |> map { value -> (total: Int32?, recent: Int32?) in - return (nil, value) - } - } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map { value -> (total: Int32?, recent: Int32?) in - return (value.total, value.recent) - } - } - } else { - return .single((nil, nil)) - } - } - onlineMemberCount = recentOnlineSignal - } - - let hasSearchTags: Signal - if let peerId = chatLocation.peerId, peerId == context.account.peerId { - hasSearchTags = context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId) - ) - |> map { tags -> Bool in - return !tags.isEmpty - } - |> distinctUntilChanged - } else { - hasSearchTags = .single(false) - } - - let hasSavedChats: Signal - if case .peer(context.account.peerId) = chatLocation { - hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved() - } else { - hasSavedChats = .single(false) - } - - let isPremiumRequiredForMessaging: Signal - if let peerId = chatLocation.peerId { - isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId) - |> distinctUntilChanged - } else { - isPremiumRequiredForMessaging = .single(false) - } - - let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy()) - - self.titleDisposable.set(nil) - var isFirstTimeValue = true - self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), - peerView, - messageAndTopic, - savedMessagesPeer, - onlineMemberCount, - hasScheduledMessages, - hasSearchTags, - hasSavedChats, - isPremiumRequiredForMessaging, - managingBot, - globalPrivacySettings - ) - |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, globalPrivacySettings in - if let strongSelf = self { - let isFirstTime = isFirstTimeValue - isFirstTimeValue = false - - strongSelf.hasScheduledMessages = hasScheduledMessages - - var renderedPeer: RenderedPeer? - var contactStatus: ChatContactStatus? - var copyProtectionEnabled = false - var businessIntro: TelegramBusinessIntro? - var sendPaidMessageStars: StarsAmount? - var alwaysShowGiftButton = false - var disallowedGifts: TelegramDisallowedGifts? - if let peer = peerView.peers[peerView.peerId] { - copyProtectionEnabled = peer.isCopyProtectionEnabled - if let cachedData = peerView.cachedData as? CachedUserData { - contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot) - if case let .known(value) = cachedData.businessIntro { - businessIntro = value - } - if cachedData.disallowedGifts != .All { - alwaysShowGiftButton = globalPrivacySettings.displayGiftButton || cachedData.flags.contains(.displayGiftButton) - } - disallowedGifts = cachedData.disallowedGifts - } else if let cachedData = peerView.cachedData as? CachedGroupData { - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) - } else if let cachedData = peerView.cachedData as? CachedChannelData { - var canReportIrrelevantLocation = true - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { - canReportIrrelevantLocation = false - } - canReportIrrelevantLocation = false - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) - - if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { - if channel.flags.contains(.isCreator) || channel.adminRights != nil { - } else { - sendPaidMessageStars = channel.sendPaidMessageStars - } - } - } - - var peers = SimpleDictionary() - peers[peer.id] = peer - if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { - peers[associatedPeer.id] = associatedPeer - } - renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media) - } - - if let savedMessagesPeerId { - var peerPresences: [PeerId: PeerPresence] = [:] - if let presence = savedMessagesPeer?.presence { - peerPresences[savedMessagesPeerId] = presence._asPresence() - } - let mappedPeerData = ChatTitleContent.PeerData( - peerId: savedMessagesPeerId, - peer: savedMessagesPeer?.peer?._asPeer(), - isContact: true, - isSavedMessages: true, - notificationSettings: nil, - peerPresences: peerPresences, - cachedData: nil - ) - - var customMessageCount: Int? - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.isMonoForum { - } else { - customMessageCount = savedMessagesPeer?.messageCount ?? 0 - } - - strongSelf.chatTitleView?.titleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true) - - strongSelf.peerView = peerView - - let imageOverride: AvatarNodeImageOverride? - if strongSelf.context.account.peerId == savedMessagesPeerId { - imageOverride = .myNotesIcon - } else if let peer = savedMessagesPeer?.peer, peer.id.isReplies { - imageOverride = .repliesIcon - } else if let peer = savedMessagesPeer?.peer, peer.id.isAnonymousSavedMessages { - imageOverride = .anonymousSavedMessagesIcon(isColored: true) - } else if let peer = savedMessagesPeer?.peer, peer.isDeleted { - imageOverride = .deletedIcon - } else { - imageOverride = nil - } - - if strongSelf.isNodeLoaded { - strongSelf.chatDisplayNode.overlayTitle = strongSelf.overlayTitle - } - - let animated = false - strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { - return $0.updatedPeer { _ in - return renderedPeer - }.updatedSavedMessagesTopicPeer(savedMessagesPeer?.peer) - .updatedHasSearchTags(hasSearchTags) - .updatedHasSavedChats(hasSavedChats) - .updatedHasScheduledMessages(hasScheduledMessages) - }) - - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: savedMessagesPeer?.peer, overrideImage: imageOverride, synchronousLoad: isFirstTime) - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false - strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile - } else { - let message = messageAndTopic.message - - var count = 0 - if let message = message { - for attribute in message.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute { - count = Int(attribute.count) - break - } - } - } - - var peerIsMuted = false - if let threadData = messageAndTopic.threadData { - if case let .muted(until) = threadData.notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - peerIsMuted = true - } - } else if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - peerIsMuted = true - } - } - - if let threadInfo = messageAndTopic.threadData?.info { - strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true) - - let avatarContent: EmojiStatusComponent.Content - if chatLocation.threadId == 1 { - avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme), tintColor: nil) - } else if let fileId = threadInfo.icon { - avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1)) - } else { - avatarContent = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: 32.0, height: 32.0)) - } - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setStatus(context: strongSelf.context, content: avatarContent) - } else { - strongSelf.chatTitleView?.titleContent = .replyThread(type: replyThreadType, count: count) - } - - var wasGroupChannel: Bool? - if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - wasGroupChannel = true - } else { - wasGroupChannel = false - } - } - var isGroupChannel: Bool? - if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - isGroupChannel = true - } else { - isGroupChannel = false - } - } - let firstTime = strongSelf.peerView == nil - - if wasGroupChannel != isGroupChannel { - if let isGroupChannel = isGroupChannel, isGroupChannel { - let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(engine: strongSelf.context.engine, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(engine: strongSelf.context.engine, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let disposable = DisposableSet() - disposable.add(recentDisposable) - disposable.add(adminsDisposable) - strongSelf.chatAdditionalDataDisposable.set(disposable) - } else { - strongSelf.chatAdditionalDataDisposable.set(nil) - } - } - - strongSelf.peerView = peerView - strongSelf.threadInfo = messageAndTopic.threadData?.info - - if strongSelf.isNodeLoaded { - strongSelf.chatDisplayNode.overlayTitle = strongSelf.overlayTitle - } - if case .standard(.previewing) = strongSelf.mode { - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false - } else { - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = true - } - - var peerDiscussionId: PeerId? - var peerGeoLocation: PeerGeoLocation? - var currentSendAsPeerId: PeerId? - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { - if peer.isMonoForum { - if let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { - currentSendAsPeerId = peer.linkedMonoforumId - } else { - currentSendAsPeerId = nil - } - } else { - currentSendAsPeerId = cachedData.sendAsPeerId - if case .group = peer.info { - peerGeoLocation = cachedData.peerGeoLocation - } - if case let .known(value) = cachedData.linkedDiscussionPeerId { - peerDiscussionId = value - } - } - } - - var isNotAccessible: Bool = false - if let cachedChannelData = peerView.cachedData as? CachedChannelData { - isNotAccessible = cachedChannelData.isNotAccessible - } - - if firstTime && isNotAccessible { - strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) - } - - var hasBots: Bool = false - if let peer = peerView.peers[peerView.peerId] { - if let cachedGroupData = peerView.cachedData as? CachedGroupData { - if !cachedGroupData.botInfos.isEmpty { - hasBots = true - } - } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { - if !cachedChannelData.botInfos.isEmpty { - hasBots = true - } - } - } - - let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive - - var explicitelyCanPinMessages: Bool = false - if let cachedUserData = peerView.cachedData as? CachedUserData { - explicitelyCanPinMessages = cachedUserData.canPinMessages - } else if peerView.peerId == context.account.peerId { - explicitelyCanPinMessages = true - } - - var animated = false - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { - animated = true - } - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { - if peer.participationStatus != updated.participationStatus { - animated = true - } - } - - var didDisplayActionsPanel = false - if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - didDisplayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.suggestAddMembers) { - didDisplayActionsPanel = true - } - } - } - if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil { - didDisplayActionsPanel = true - } - - var displayActionsPanel = false - if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.suggestAddMembers) { - displayActionsPanel = true - } - } - } - if let contactStatus, contactStatus.managingBot != nil { - displayActionsPanel = true - } - - if displayActionsPanel != didDisplayActionsPanel { - animated = true - } - - if strongSelf.preloadHistoryPeerId != peerDiscussionId { - strongSelf.preloadHistoryPeerId = peerDiscussionId - if let peerDiscussionId = peerDiscussionId { - strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) - } else { - strongSelf.preloadHistoryPeerIdDisposable.set(nil) - } - } - - var appliedBoosts: Int32? - var boostsToUnrestrict: Int32? - if let cachedChannelData = peerView.cachedData as? CachedChannelData { - appliedBoosts = cachedChannelData.appliedBoosts - boostsToUnrestrict = cachedChannelData.boostsToUnrestrict - } - - if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId { - strongSelf.premiumOrStarsRequiredDisposable = ((strongSelf.context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone() - } - - strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { - return $0.updatedPeer { _ in - return renderedPeer - }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages).updatedCurrentSendAsPeerId(currentSendAsPeerId) - .updatedCopyProtectionEnabled(copyProtectionEnabled) - .updatedHasSearchTags(hasSearchTags) - .updatedIsPremiumRequiredForMessaging(isPremiumRequiredForMessaging) - .updatedHasSavedChats(hasSavedChats) - .updatedAppliedBoosts(appliedBoosts) - .updatedBoostsToUnrestrict(boostsToUnrestrict) - .updatedBusinessIntro(businessIntro) - .updatedSendPaidMessageStars(sendPaidMessageStars) - .updatedAlwaysShowGiftButton(alwaysShowGiftButton) - .updatedDisallowedGifts(disallowedGifts) - .updatedInterfaceState { interfaceState in - var interfaceState = interfaceState - - if let channel = renderedPeer?.peer as? TelegramChannel { - if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } else if channel.hasBannedPermission(.banSendVoice) != nil { - if channel.hasBannedPermission(.banSendInstantVideos) == nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) - } - } else if channel.hasBannedPermission(.banSendInstantVideos) != nil { - if channel.hasBannedPermission(.banSendVoice) == nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } - } - } else if let group = renderedPeer?.peer as? TelegramGroup { - if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } else if group.hasBannedPermission(.banSendVoice) { - if !group.hasBannedPermission(.banSendInstantVideos) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) - } - } else if group.hasBannedPermission(.banSendInstantVideos) { - if !group.hasBannedPermission(.banSendVoice) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } - } - } - - return interfaceState - } - }) - - if let replyThreadId, let channel = renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, strongSelf.nextChannelToReadDisposable == nil { - strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(), - strongSelf.context.engine.peers.getNextUnreadForumTopic(peerId: channel.id, topicId: Int32(clamping: replyThreadId)), - ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager) - ) - |> then(.complete() |> delay(1.0, queue: .mainQueue())) - |> restart).startStrict(next: { nextThreadData, nextChatSuggestionTip in - guard let strongSelf = self else { - return - } - - strongSelf.offerNextChannelToRead = true - strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextThreadData.flatMap { nextThreadData -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in - return (peer: EnginePeer(channel), threadData: nextThreadData, unreadCount: Int(nextThreadData.data.incomingUnreadCount), location: .same) - } - strongSelf.chatDisplayNode.historyNode.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 - - strongSelf.updateNextChannelToReadVisibility() - }) - } - } - if !strongSelf.didSetChatLocationInfoReady { - strongSelf.didSetChatLocationInfoReady = true - strongSelf._chatLocationInfoReady.set(.single(true)) - } - isReady?.set(.single(true)) - } - })) - } else if case .customChatContents = self.chatLocationInfoData { - self.reportIrrelvantGeoNoticePromise.set(.single(nil)) - self.titleDisposable.set(nil) - - let peerView: Signal = .single(nil) - - if case let .customChatContents(customChatContents) = self.subject { - switch customChatContents.kind { - case .hashTagSearch: - break - case let .quickReplyMessageInput(shortcut, shortcutType): - switch shortcutType { - case .generic: - self.chatTitleView?.titleContent = .custom("\(shortcut)", nil, false) - case .greeting: - self.chatTitleView?.titleContent = .custom(self.presentationData.strings.QuickReply_TitleGreetingMessage, nil, false) - case .away: - self.chatTitleView?.titleContent = .custom(self.presentationData.strings.QuickReply_TitleAwayMessage, nil, false) - } - case let .businessLinkSetup(link): - let linkUrl: String - if link.url.hasPrefix("https://") { - linkUrl = String(link.url[link.url.index(link.url.startIndex, offsetBy: "https://".count)...]) - } else { - linkUrl = link.url - } - - self.chatTitleView?.titleContent = .custom(link.title ?? self.presentationData.strings.Business_Links_EditLinkTitle, linkUrl, false) - } - } else { - self.chatTitleView?.titleContent = .custom(" ", nil, false) - } - - var isFirstTimeValue = true - self.peerDisposable.set((peerView - |> deliverOnMainQueue).startStrict(next: { [weak self] peerView in - guard let self else { - return - } - - let isFirstTime = isFirstTimeValue - isFirstTimeValue = false - - var renderedPeer: RenderedPeer? - if let peerView, let peer = peerView.peers[peerView.peerId] { - var peers = SimpleDictionary() - peers[peer.id] = peer - if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { - peers[associatedPeer.id] = associatedPeer - } - renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media) - - (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: self.context, theme: self.presentationData.theme, peer: EnginePeer(peer), overrideImage: nil, synchronousLoad: isFirstTime) - } - - self.peerView = peerView - - if self.isNodeLoaded { - self.chatDisplayNode.overlayTitle = self.overlayTitle - } - (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false - - self.updateChatPresentationInterfaceState(animated: false, interactive: false, { - return $0.updatedPeer { _ in - return renderedPeer - }.updatedInterfaceState { interfaceState in - return interfaceState - } - }) - - if !self.didSetChatLocationInfoReady { - self.didSetChatLocationInfoReady = true - self._chatLocationInfoReady.set(.single(true)) - } - isReady?.set(.single(true)) - })) - } - } - - func reloadCachedData() { - let initialData = self.chatDisplayNode.historyNode.initialData + let contentData = ChatControllerImpl.ContentData( + context: self.context, + chatLocation: chatLocation, + chatLocationContextHolder: chatLocationContextHolder, + initialSubject: self.subject, + mode: self.mode, + configuration: configuration, + adMessagesContext: self.chatDisplayNode.adMessagesContext, + currentChatListFilter: self.currentChatListFilter, + customChatNavigationStack: self.customChatNavigationStack, + presentationData: self.presentationData, + historyNode: historyNode + ) + self.pendingContentData = (contentData, historyNode) + self.contentDataDisposable = (contentData.isReady.get() + |> filter { $0 } |> take(1) - |> beforeNext { [weak self] combinedInitialData in - guard let strongSelf = self, let combinedInitialData = combinedInitialData else { + |> deliverOnMainQueue).startStrict(next: { [weak self, weak contentData] _ in + guard let self, let contentData, self.pendingContentData?.contentData === contentData else { return } - - if let opaqueState = (combinedInitialData.initialData?.storedInterfaceState).flatMap(_internal_decodeStoredChatInterfaceState) { - var interfaceState = ChatInterfaceState.parse(opaqueState) - - var pinnedMessageId: MessageId? - var peerIsBlocked: Bool = false - var callsAvailable: Bool = true - var callsPrivate: Bool = false - var activeGroupCallInfo: ChatActiveGroupCallInfo? - var slowmodeState: ChatSlowmodeState? - if let cachedData = combinedInitialData.cachedData as? CachedChannelData { - pinnedMessageId = cachedData.pinnedMessageId - - var canBypassRestrictions = false - if let boostsToUnrestrict = cachedData.boostsToUnrestrict, let appliedBoosts = cachedData.appliedBoosts, appliedBoosts >= boostsToUnrestrict { - canBypassRestrictions = true - } - if !canBypassRestrictions, let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { - if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: strongSelf.context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { - slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) + self.contentData = contentData + self.pendingContentData = nil + self.contentDataUpdated(synchronous: true, previousState: contentData.state) + + self.chatThemeEmoticonPromise.set(contentData.chatThemeEmoticonPromise.get()) + self.chatWallpaperPromise.set(contentData.chatWallpaperPromise.get()) + + self.contentDataReady.set(true) + + contentData.onUpdated = { [weak self, weak contentData] previousState in + guard let self, let contentData, self.contentData === contentData else { + return + } + self.contentDataUpdated(synchronous: false, previousState: previousState) + } + }) + } + + func contentDataUpdated(synchronous: Bool, previousState: ContentData.State) { + guard let contentData = self.contentData else { + return + } + self.navigationBar?.userInfo = contentData.state.navigationUserInfo + + if self.chatTitleView?.titleContent != contentData.state.chatTitleContent { + var animateTitleContents = false + if !synchronous, case let .messageOptions(_, _, info) = self.subject, case .reply = info { + animateTitleContents = true + } + if animateTitleContents && self.chatTitleView?.titleContent != nil { + self.chatTitleView?.animateLayoutTransition() + } + self.chatTitleView?.titleContent = contentData.state.chatTitleContent + } + + if let infoAvatar = contentData.state.infoAvatar { + switch infoAvatar { + case let .peer(peer, imageOverride, contextActionIsEnabled, accessibilityLabel): + (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: self.context, theme: self.presentationData.theme, peer: peer, overrideImage: imageOverride, synchronousLoad: synchronous) + (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = contextActionIsEnabled + self.chatInfoNavigationButton?.buttonItem.accessibilityLabel = accessibilityLabel + case let .emojiStatus(content, contextActionIsEnabled): + (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setStatus(context: self.context, content: content) + (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = contextActionIsEnabled + self.chatInfoNavigationButton?.buttonItem.accessibilityLabel = self.presentationData.strings.Conversation_ContextMenuOpenProfile + } + } else { + (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false + } + + if let avatarNode = self.avatarNode { + avatarNode.avatarNode.setStoryStats(storyStats: contentData.state.storyStats.flatMap { storyStats -> AvatarNode.StoryStats? in + if storyStats.totalCount == 0 { + return nil + } + if storyStats.unseenCount == 0 { + return nil + } + return AvatarNode.StoryStats( + totalCount: storyStats.totalCount, + unseenCount: storyStats.unseenCount, + hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends + ) + }, presentationParams: AvatarNode.StoryPresentationParams( + colors: AvatarNode.Colors(theme: self.presentationData.theme), + lineWidth: 1.5, + inactiveLineWidth: 1.5 + ), transition: .immediate) + } + + self.chatDisplayNode.overlayTitle = contentData.overlayTitle + + self.chatDisplayNode.historyNode.nextChannelToRead = contentData.state.nextChannelToRead + self.chatDisplayNode.historyNode.nextChannelToReadDisplayName = contentData.state.nextChannelToReadDisplayName + self.updateNextChannelToReadVisibility() + + var animated = false + if self.presentationInterfaceState.adMessage?.id != contentData.state.adMessage?.id { + animated = true + } + if let peer = previousState.renderedPeer?.peer as? TelegramSecretChat, let updated = contentData.state.renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { + animated = true + } + if let peer = previousState.renderedPeer?.peer as? TelegramChannel, let updated = contentData.state.renderedPeer?.peer as? TelegramChannel { + if peer.participationStatus != updated.participationStatus { + animated = true + } + } + + var didDisplayActionsPanel = false + if let contactStatus = previousState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.suggestAddMembers) { + didDisplayActionsPanel = true + } + } + } + if let contactStatus = previousState.contactStatus, contactStatus.managingBot != nil { + didDisplayActionsPanel = true + } + if self.presentationInterfaceState.search != nil && self.presentationInterfaceState.hasSearchTags { + didDisplayActionsPanel = true + } + + var displayActionsPanel = false + if let contactStatus = contentData.state.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.suggestAddMembers) { + displayActionsPanel = true + } + } + } + if let contactStatus = contentData.state.contactStatus, contactStatus.managingBot != nil { + displayActionsPanel = true + } + if self.presentationInterfaceState.search != nil && contentData.state.hasSearchTags { + displayActionsPanel = true + } + if displayActionsPanel != didDisplayActionsPanel { + animated = true + } + + if previousState.pinnedMessage != contentData.state.pinnedMessage { + animated = true + } + + self.updateChatPresentationInterfaceState(animated: animated && self.willAppear, interactive: false, { presentationInterfaceState in + var presentationInterfaceState = presentationInterfaceState + presentationInterfaceState = presentationInterfaceState.updatedPeer({ _ in + return contentData.state.renderedPeer + }) + presentationInterfaceState = presentationInterfaceState.updatedIsNotAccessible(contentData.state.isNotAccessible) + presentationInterfaceState = presentationInterfaceState.updatedContactStatus(contentData.state.contactStatus) + presentationInterfaceState = presentationInterfaceState.updatedHasBots(contentData.state.hasBots) + presentationInterfaceState = presentationInterfaceState.updatedHasBotCommands(contentData.state.hasBotCommands) + presentationInterfaceState = presentationInterfaceState.updatedBotMenuButton(contentData.state.botMenuButton) + presentationInterfaceState = presentationInterfaceState.updatedIsArchived(contentData.state.isArchived) + presentationInterfaceState = presentationInterfaceState.updatedPeerIsMuted(contentData.state.peerIsMuted) + presentationInterfaceState = presentationInterfaceState.updatedPeerDiscussionId(contentData.state.peerDiscussionId) + presentationInterfaceState = presentationInterfaceState.updatedPeerGeoLocation(contentData.state.peerGeoLocation) + presentationInterfaceState = presentationInterfaceState.updatedExplicitelyCanPinMessages(contentData.state.explicitelyCanPinMessages) + presentationInterfaceState = presentationInterfaceState.updatedHasScheduledMessages(contentData.state.hasScheduledMessages) + presentationInterfaceState = presentationInterfaceState.updatedAutoremoveTimeout(contentData.state.autoremoveTimeout) + presentationInterfaceState = presentationInterfaceState.updatedCurrentSendAsPeerId(contentData.state.currentSendAsPeerId) + presentationInterfaceState = presentationInterfaceState.updatedCopyProtectionEnabled(contentData.state.copyProtectionEnabled) + presentationInterfaceState = presentationInterfaceState.updatedHasSearchTags(contentData.state.hasSearchTags) + presentationInterfaceState = presentationInterfaceState.updatedIsPremiumRequiredForMessaging(contentData.state.isPremiumRequiredForMessaging) + presentationInterfaceState = presentationInterfaceState.updatedSendPaidMessageStars(contentData.state.sendPaidMessageStars) + presentationInterfaceState = presentationInterfaceState.updatedAlwaysShowGiftButton(contentData.state.alwaysShowGiftButton) + presentationInterfaceState = presentationInterfaceState.updatedDisallowedGifts(contentData.state.disallowedGifts) + presentationInterfaceState = presentationInterfaceState.updatedHasSavedChats(contentData.state.hasSavedChats) + presentationInterfaceState = presentationInterfaceState.updatedAppliedBoosts(contentData.state.appliedBoosts) + presentationInterfaceState = presentationInterfaceState.updatedBoostsToUnrestrict(contentData.state.boostsToUnrestrict) + presentationInterfaceState = presentationInterfaceState.updatedHasBirthdayToday(contentData.state.hasBirthdayToday) + presentationInterfaceState = presentationInterfaceState.updatedBusinessIntro(contentData.state.businessIntro) + presentationInterfaceState = presentationInterfaceState.updatedAdMessage(contentData.state.adMessage) + presentationInterfaceState = presentationInterfaceState.updatedPeerVerification(contentData.state.peerVerification) + presentationInterfaceState = presentationInterfaceState.updatedStarGiftsAvailable(contentData.state.starGiftsAvailable) + presentationInterfaceState = presentationInterfaceState.updatedSavedMessagesTopicPeer(contentData.state.savedMessagesTopicPeer) + presentationInterfaceState = presentationInterfaceState.updatedKeyboardButtonsMessage(contentData.state.keyboardButtonsMessage) + presentationInterfaceState = presentationInterfaceState.updatedPinnedMessageId(contentData.state.pinnedMessageId) + presentationInterfaceState = presentationInterfaceState.updatedPinnedMessage(contentData.state.pinnedMessage) + presentationInterfaceState = presentationInterfaceState.updatedPeerIsBlocked(contentData.state.peerIsBlocked) + presentationInterfaceState = presentationInterfaceState.updatedCallsAvailable(contentData.state.callsAvailable) + presentationInterfaceState = presentationInterfaceState.updatedCallsPrivate(contentData.state.callsPrivate) + presentationInterfaceState = presentationInterfaceState.updatedActiveGroupCallInfo(contentData.state.activeGroupCallInfo) + presentationInterfaceState = presentationInterfaceState.updatedSlowmodeState(contentData.state.slowmodeState) + presentationInterfaceState = presentationInterfaceState.updatedSuggestPremiumGift(contentData.state.suggestPremiumGift) + presentationInterfaceState = presentationInterfaceState.updatedTranslationState(contentData.state.translationState) + presentationInterfaceState = presentationInterfaceState.updatedVoiceMessagesAvailable(contentData.state.voiceMessagesAvailable) + presentationInterfaceState = presentationInterfaceState.updatedCustomEmojiAvailable(contentData.state.customEmojiAvailable) + presentationInterfaceState = presentationInterfaceState.updatedThreadData(contentData.state.threadData) + presentationInterfaceState = presentationInterfaceState.updatedForumTopicData(contentData.state.forumTopicData) + presentationInterfaceState = presentationInterfaceState.updatedIsGeneralThreadClosed(contentData.state.isGeneralThreadClosed) + presentationInterfaceState = presentationInterfaceState.updatedPremiumGiftOptions(contentData.state.premiumGiftOptions) + + presentationInterfaceState = presentationInterfaceState.updatedTitlePanelContext({ context in + if contentData.state.pinnedMessageId != nil { + if !context.contains(where: { item in + switch item { + case .pinnedMessage: + return true + default: + return false } + }) { + var updatedContexts = context + updatedContexts.append(.pinnedMessage) + return updatedContexts.sorted() + } else { + return context } - if let activeCall = cachedData.activeCall { - activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) + } else { + if let index = context.firstIndex(where: { item in + switch item { + case .pinnedMessage: + return true + default: + return false + } + }) { + var updatedContexts = context + updatedContexts.remove(at: index) + return updatedContexts + } else { + return context } - } else if let cachedData = combinedInitialData.cachedData as? CachedUserData { - peerIsBlocked = cachedData.isBlocked - callsAvailable = cachedData.voiceCallsAvailable - callsPrivate = cachedData.callsPrivate - pinnedMessageId = cachedData.pinnedMessageId - } else if let cachedData = combinedInitialData.cachedData as? CachedGroupData { - pinnedMessageId = cachedData.pinnedMessageId - if let activeCall = cachedData.activeCall { - activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) - } - } else if let _ = combinedInitialData.cachedData as? CachedSecretChatData { + } + }) + presentationInterfaceState = presentationInterfaceState.updatedTitlePanelContext { context in + var peers: [EnginePeer] = [] + if let requestsState = contentData.state.requestsState { + peers = Array(requestsState.importers.compactMap({ $0.peer.peer.flatMap({ EnginePeer($0) }) }).prefix(3)) } - if let channel = combinedInitialData.initialData?.peer as? TelegramChannel { + var peersDismissed = false + if let dismissedInvitationRequests = contentData.state.dismissedInvitationRequests, Set(peers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) { + peersDismissed = true + } + + if let requestsState = contentData.state.requestsState, requestsState.count > 0 && !peersDismissed { + if !context.contains(where: { + switch $0 { + case .inviteRequests(peers, requestsState.count): + return true + default: + return false + } + }) { + var updatedContexts = context.filter { c in + if case .inviteRequests = c { + return false + } else { + return true + } + } + updatedContexts.append(.inviteRequests(peers, requestsState.count)) + return updatedContexts.sorted() + } else { + return context + } + } else { + if let index = context.firstIndex(where: { item in + switch item { + case .inviteRequests: + return true + default: + return false + } + }) { + var updatedContexts = context + updatedContexts.remove(at: index) + return updatedContexts + } else { + return context + } + } + } + + let initialInterfaceState = contentData.initialInterfaceState + contentData.initialInterfaceState = nil + + presentationInterfaceState = presentationInterfaceState.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + if let initialInterfaceState { + interfaceState = initialInterfaceState.interfaceState + } + + if let channel = contentData.state.renderedPeer?.peer as? TelegramChannel { if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) } else if channel.hasBannedPermission(.banSendVoice) != nil { @@ -1860,7 +455,7 @@ extension ChatControllerImpl { interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) } } - } else if let group = combinedInitialData.initialData?.peer as? TelegramGroup { + } else if let group = contentData.state.renderedPeer?.peer as? TelegramGroup { if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) { interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) } else if group.hasBannedPermission(.banSendVoice) { @@ -1874,131 +469,65 @@ extension ChatControllerImpl { } } - if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation { - if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isForumOrMonoForum { - pinnedMessageId = nil - } else { - pinnedMessageId = replyThreadMessageId.effectiveTopId - } - } - - var pinnedMessage: ChatPinnedMessage? - if let pinnedMessageId = pinnedMessageId { - if let cachedDataMessages = combinedInitialData.cachedDataMessages { - if let message = cachedDataMessages[pinnedMessageId] { - pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id) - } - } - } - - var buttonKeyboardMessage = combinedInitialData.buttonKeyboardMessage - if let buttonKeyboardMessageValue = buttonKeyboardMessage, buttonKeyboardMessageValue.isRestricted(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with({ $0 })) { - buttonKeyboardMessage = nil - } - - strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { updated in - var updated = updated - - updated = updated.updatedInterfaceState({ _ in return interfaceState }) - - updated = updated.updatedKeyboardButtonsMessage(buttonKeyboardMessage) - updated = updated.updatedPinnedMessageId(pinnedMessageId) - updated = updated.updatedPinnedMessage(pinnedMessage) - updated = updated.updatedPeerIsBlocked(peerIsBlocked) - updated = updated.updatedCallsAvailable(callsAvailable) - updated = updated.updatedCallsPrivate(callsPrivate) - updated = updated.updatedActiveGroupCallInfo(activeGroupCallInfo) - updated = updated.updatedTitlePanelContext({ context in - if pinnedMessageId != nil { - if !context.contains(where: { - switch $0 { - case .pinnedMessage: - return true - default: - return false - } - }) { - var updatedContexts = context - updatedContexts.append(.pinnedMessage) - return updatedContexts.sorted() - } else { - return context - } - } else { - if let index = context.firstIndex(where: { - switch $0 { - case .pinnedMessage: - return true - default: - return false - } - }) { - var updatedContexts = context - updatedContexts.remove(at: index) - return updatedContexts - } else { - return context - } - } - }) - if let editMessage = interfaceState.editMessage, let message = combinedInitialData.initialData?.associatedMessages[editMessage.messageId] { - let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: strongSelf.context, state: updated, message: message) - updated = updatedState - strongSelf.editingUrlPreviewQueryState?.1.dispose() - strongSelf.editingUrlPreviewQueryState = updatedPreviewQueryState - } - updated = updated.updatedSlowmodeState(slowmodeState) - return updated - }) + return interfaceState } - if let readStateData = combinedInitialData.readStateData { - if case let .peer(peerId) = strongSelf.chatLocation, let peerReadStateData = readStateData[peerId], let notificationSettings = peerReadStateData.notificationSettings { - - let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: peerReadStateData.totalState ?? ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:])) - - var globalRemainingUnreadChatCount = count - if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && peerReadStateData.unreadCount > 0 { - if case .messages = inAppSettings.totalUnreadCountDisplayCategory { - globalRemainingUnreadChatCount -= peerReadStateData.unreadCount - } else { - globalRemainingUnreadChatCount -= 1 - } - } - if globalRemainingUnreadChatCount > 0 { - strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" - } else { - strongSelf.navigationItem.badge = "" - } - } + + if let editMessage = initialInterfaceState?.editMessage { + let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: self.context, state: presentationInterfaceState, message: editMessage) + presentationInterfaceState = updatedState + self.editingUrlPreviewQueryState?.1.dispose() + self.editingUrlPreviewQueryState = updatedPreviewQueryState } + + return presentationInterfaceState + }) + + if let initialNavigationBadge = contentData.initialNavigationBadge { + contentData.initialNavigationBadge = nil + self.navigationItem.badge = initialNavigationBadge } - let effectiveCachedDataReady: Signal - if case .replyThread = self.chatLocation { - effectiveCachedDataReady = self.cachedDataReady.get() - } else { - effectiveCachedDataReady = self.cachedDataReady.get() + if let performDismissAction = contentData.state.performDismissAction, !self.didHandlePerformDismissAction { + self.didHandlePerformDismissAction = true + + switch performDismissAction { + case let .upgraded(upgradedToPeerId): + if let navigationController = self.effectiveNavigationController { + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === self }) { + viewControllers[index] = ChatControllerImpl(context: self.context, chatLocation: .peer(id: upgradedToPeerId)) + navigationController.setViewControllers(viewControllers, animated: false) + } + } + case .movedToForumTopics: + if let peerView = contentData.state.peerView, let navigationController = self.effectiveNavigationController { + let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .forum(peerId: peerView.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) + navigationController.replaceController(self, with: chatListController, animated: true) + } + case .dismiss: + self.dismiss() + } } + } + + func reloadCachedData() { var measure_isFirstTime = true + #if DEBUG let initTimestamp = self.initTimestamp + #endif - let mapped_chatLocationInfoReady = self._chatLocationInfoReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "chatLocationInfoReady") - let mapped_effectiveCachedDataReady = effectiveCachedDataReady |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "effectiveCachedDataReady") - let mapped_initialDataReady = initialData |> map { $0 != nil } |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "initialDataReady") - let mapped_wallpaperReady = self.wallpaperReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "wallpaperReady") - let mapped_presentationReady = self.presentationReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "presentationReady") + //TODO:release ready must include chat node - self.ready.set(combineLatest(queue: .mainQueue(), - mapped_chatLocationInfoReady, - mapped_effectiveCachedDataReady, - mapped_initialDataReady, - mapped_wallpaperReady, - mapped_presentationReady - ) - |> map { chatLocationInfoReady, cachedDataReady, initialData, wallpaperReady, presentationReady in - return chatLocationInfoReady && cachedDataReady && initialData && wallpaperReady && presentationReady + self.ready.set(combineLatest(queue: .mainQueue(), [ + self.contentDataReady.get(), + self.wallpaperReady.get(), + self.presentationReady.get() + ]) + |> map { values in + return !values.contains(where: { !$0 }) } + |> filter { $0 } + |> take(1) |> distinctUntilChanged |> beforeNext { value in if measure_isFirstTime { @@ -2009,451 +538,6 @@ extension ChatControllerImpl { #endif } }) - - self.buttonKeyboardMessageDisposable?.dispose() - self.buttonKeyboardMessageDisposable = self.chatDisplayNode.historyNode.buttonKeyboardMessage.startStrict(next: { [weak self] message in - if let strongSelf = self { - var buttonKeyboardMessageUpdated = false - if let currentButtonKeyboardMessage = strongSelf.presentationInterfaceState.keyboardButtonsMessage, let message = message { - if currentButtonKeyboardMessage.id != message.id || currentButtonKeyboardMessage.stableVersion != message.stableVersion { - buttonKeyboardMessageUpdated = true - } - } else if (strongSelf.presentationInterfaceState.keyboardButtonsMessage != nil) != (message != nil) { - buttonKeyboardMessageUpdated = true - } - if buttonKeyboardMessageUpdated { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedKeyboardButtonsMessage(message) }) - } - } - }) - - if let peerId = self.chatLocation.peerId { - let customEmojiAvailable: Signal = self.context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Peer.SecretChatLayer(id: peerId) - ) - |> map { layer -> Bool in - guard let layer = layer else { - return true - } - - return layer >= 144 - } - |> distinctUntilChanged - - let isForum = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> map { peer -> Bool in - if case let .channel(channel) = peer { - return channel.isForumOrMonoForum - } else { - return false - } - } - |> distinctUntilChanged - - let context = self.context - let threadData: Signal - let forumTopicData: Signal - if let threadId = self.chatLocation.threadId { - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) - threadData = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> ChatPresentationInterfaceState.ThreadData? in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return nil - } - guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { - return nil - } - return ChatPresentationInterfaceState.ThreadData(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor, isOwnedByMe: data.isOwnedByMe, isClosed: data.isClosed) - } - |> distinctUntilChanged - forumTopicData = .single(nil) - } else { - forumTopicData = isForum - |> mapToSignal { isForum -> Signal in - if isForum { - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: 1) - return context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> ChatPresentationInterfaceState.ThreadData? in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return nil - } - guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { - return nil - } - return ChatPresentationInterfaceState.ThreadData(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor, isOwnedByMe: data.isOwnedByMe, isClosed: data.isClosed) - } - |> distinctUntilChanged - } else { - return .single(nil) - } - } - threadData = .single(nil) - } - - if case .standard(.previewing) = self.presentationInterfaceState.mode { - - } else if peerId.namespace != Namespaces.Peer.SecretChat && peerId != context.account.peerId && self.subject != .scheduledMessages { - self.premiumGiftSuggestionDisposable?.dispose() - self.premiumGiftSuggestionDisposable = (ApplicationSpecificNotice.dismissedPremiumGiftSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId) - |> deliverOnMainQueue).startStrict(next: { [weak self] timestamp in - if let strongSelf = self { - let currentTime = Int32(Date().timeIntervalSince1970) - strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in - var suggest = true - if let timestamp, currentTime < timestamp + 60 * 60 * 24 { - suggest = false - } - return state.updatedSuggestPremiumGift(suggest) - }) - } - }) - - var baseLanguageCode = self.presentationData.strings.baseLanguageCode - if baseLanguageCode.contains("-") { - baseLanguageCode = baseLanguageCode.components(separatedBy: "-").first ?? baseLanguageCode - } - let isPremium = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> map { peer -> Bool in - return peer?.isPremium ?? false - } |> distinctUntilChanged - - let isHidden = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.TranslationHidden(id: peerId)) - |> distinctUntilChanged - - let hasAutoTranslate = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AutoTranslateEnabled(id: peerId)) - |> distinctUntilChanged - - self.translationStateDisposable?.dispose() - let chatLocation = self.chatLocation - self.translationStateDisposable = (combineLatest( - queue: .concurrentDefaultQueue(), - isPremium, - isHidden, - hasAutoTranslate, - ApplicationSpecificNotice.translationSuggestion(accountManager: self.context.sharedContext.accountManager) - ) |> mapToSignal { isPremium, isHidden, hasAutoTranslate, counterAndTimestamp -> Signal in - var maybeSuggestPremium = false - if counterAndTimestamp.0 >= 3 { - maybeSuggestPremium = true - } - if (isPremium || maybeSuggestPremium || hasAutoTranslate) && !isHidden { - return chatTranslationState(context: context, peerId: peerId, threadId: chatLocation.threadId) - |> map { translationState -> ChatPresentationTranslationState? in - if let translationState, !translationState.fromLang.isEmpty && (translationState.fromLang != baseLanguageCode || translationState.isEnabled) { - return ChatPresentationTranslationState(isEnabled: translationState.isEnabled, fromLang: translationState.fromLang, toLang: translationState.toLang ?? baseLanguageCode) - } else { - return nil - } - } - |> distinctUntilChanged - } else { - return .single(nil) - } - } - |> deliverOnMainQueue).startStrict(next: { [weak self] chatTranslationState in - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in - return state.updatedTranslationState(chatTranslationState) - }) - } - }) - } - - let premiumGiftOptions: Signal<[CachedPremiumGiftOption], NoError> = .single([]) - |> then( - self.context.engine.payments.premiumGiftCodeOptions(peerId: peerId, onlyCached: true) - |> map { options in - return options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } - } - ) - - let isTopReplyThreadMessageShown: Signal = self.chatDisplayNode.historyNode.isTopReplyThreadMessageShown.get() - |> distinctUntilChanged - - let hasPendingMessages: Signal - let chatLocationPeerId = self.chatLocation.peerId - - if let chatLocationPeerId = chatLocationPeerId { - hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages - |> mapToSignal { peerIds -> Signal in - let value = peerIds.contains(chatLocationPeerId) - if value { - return .single(true) - } else { - return .single(false) - } - } - |> distinctUntilChanged - } else { - hasPendingMessages = .single(false) - } - - let topPinnedMessage: Signal - if let subject = self.subject { - switch subject { - case .messageOptions, .pinnedMessages, .scheduledMessages: - topPinnedMessage = .single(nil) - default: - topPinnedMessage = self.topPinnedMessageSignal(latest: false) - } - } else { - topPinnedMessage = self.topPinnedMessageSignal(latest: false) - } - - self.cachedDataDisposable?.dispose() - self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages |> debug_measureTimeToFirstEvent(label: "cachedData_cachedPeerDataAndMessages"), - hasPendingMessages |> debug_measureTimeToFirstEvent(label: "cachedData_hasPendingMessages"), - isTopReplyThreadMessageShown |> debug_measureTimeToFirstEvent(label: "cachedData_isTopReplyThreadMessageShown"), - topPinnedMessage |> debug_measureTimeToFirstEvent(label: "cachedData_topPinnedMessage"), - customEmojiAvailable |> debug_measureTimeToFirstEvent(label: "cachedData_customEmojiAvailable"), - isForum |> debug_measureTimeToFirstEvent(label: "cachedData_isForum"), - threadData |> debug_measureTimeToFirstEvent(label: "cachedData_threadData"), - forumTopicData |> debug_measureTimeToFirstEvent(label: "cachedData_forumTopicData"), - premiumGiftOptions |> debug_measureTimeToFirstEvent(label: "cachedData_premiumGiftOptions") - ).startStrict(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, customEmojiAvailable, isForum, threadData, forumTopicData, premiumGiftOptions in - if let strongSelf = self { - let (cachedData, messages) = cachedDataAndMessages - - if cachedData != nil { - var themeEmoticon: String? = nil - var chatWallpaper: TelegramWallpaper? - if let cachedData = cachedData as? CachedUserData { - themeEmoticon = cachedData.themeEmoticon - chatWallpaper = cachedData.wallpaper - } else if let cachedData = cachedData as? CachedGroupData { - themeEmoticon = cachedData.themeEmoticon - } else if let cachedData = cachedData as? CachedChannelData { - themeEmoticon = cachedData.themeEmoticon - chatWallpaper = cachedData.wallpaper - } - - strongSelf.chatThemeEmoticonPromise.set(.single(themeEmoticon)) - strongSelf.chatWallpaperPromise.set(.single(chatWallpaper)) - } - - var pinnedMessageId: MessageId? - var peerIsBlocked: Bool = false - var callsAvailable: Bool = false - var callsPrivate: Bool = false - var voiceMessagesAvailable: Bool = true - var slowmodeState: ChatSlowmodeState? - var activeGroupCallInfo: ChatActiveGroupCallInfo? - var inviteRequestsPending: Int32? - if let cachedData = cachedData as? CachedChannelData { - pinnedMessageId = cachedData.pinnedMessageId - if !canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState) { - if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { - if hasPendingMessages { - slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .pendingMessages) - } else if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: strongSelf.context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { - slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) - } - } - } - if let activeCall = cachedData.activeCall { - activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) - } - inviteRequestsPending = cachedData.inviteRequestsPending - } else if let cachedData = cachedData as? CachedUserData { - peerIsBlocked = cachedData.isBlocked - callsAvailable = cachedData.voiceCallsAvailable - callsPrivate = cachedData.callsPrivate - pinnedMessageId = cachedData.pinnedMessageId - voiceMessagesAvailable = cachedData.voiceMessagesAvailable - } else if let cachedData = cachedData as? CachedGroupData { - pinnedMessageId = cachedData.pinnedMessageId - if let activeCall = cachedData.activeCall { - activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) - } - inviteRequestsPending = cachedData.inviteRequestsPending - } else if let _ = cachedData as? CachedSecretChatData { - } - - var pinnedMessage: ChatPinnedMessage? - switch strongSelf.chatLocation { - case let .replyThread(replyThreadMessage): - if isForum { - pinnedMessageId = topPinnedMessage?.message.id - pinnedMessage = topPinnedMessage - } else { - if isTopReplyThreadMessageShown { - pinnedMessageId = nil - } else { - pinnedMessageId = replyThreadMessage.effectiveTopId - } - if let pinnedMessageId = pinnedMessageId { - if let message = messages?[pinnedMessageId] { - pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id) - } - } - } - case .peer: - pinnedMessageId = topPinnedMessage?.message.id - pinnedMessage = topPinnedMessage - case .customChatContents: - pinnedMessageId = nil - pinnedMessage = nil - } - - var pinnedMessageUpdated = false - if let current = strongSelf.presentationInterfaceState.pinnedMessage, let updated = pinnedMessage { - if current != updated { - pinnedMessageUpdated = true - } - } else if (strongSelf.presentationInterfaceState.pinnedMessage != nil) != (pinnedMessage != nil) { - pinnedMessageUpdated = true - } - - let callsDataUpdated = strongSelf.presentationInterfaceState.callsAvailable != callsAvailable || strongSelf.presentationInterfaceState.callsPrivate != callsPrivate - - let voiceMessagesAvailableUpdated = strongSelf.presentationInterfaceState.voiceMessagesAvailable != voiceMessagesAvailable - - var canManageInvitations = false - if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) { - canManageInvitations = true - } else if let group = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup { - if case .creator = group.role { - canManageInvitations = true - } else if case let .admin(rights, _) = group.role, rights.rights.contains(.canInviteUsers) { - canManageInvitations = true - } - } - - if canManageInvitations, let inviteRequestsPending = inviteRequestsPending, inviteRequestsPending >= 0 { - if strongSelf.inviteRequestsContext == nil { - let inviteRequestsContext = strongSelf.context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil)) - strongSelf.inviteRequestsContext = inviteRequestsContext - - strongSelf.inviteRequestsDisposable.set((combineLatest(queue: Queue.mainQueue(), inviteRequestsContext.state, ApplicationSpecificNotice.dismissedInvitationRequests(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId))).startStrict(next: { [weak self] requestsState, dismissedInvitationRequests in - guard let strongSelf = self else { - return - } - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in - return state - .updatedTitlePanelContext({ context in - let peers: [EnginePeer] = Array(requestsState.importers.compactMap({ $0.peer.peer.flatMap({ EnginePeer($0) }) }).prefix(3)) - - var peersDismissed = false - if let dismissedInvitationRequests = dismissedInvitationRequests, Set(peers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) { - peersDismissed = true - } - - if requestsState.count > 0 && !peersDismissed { - if !context.contains(where: { - switch $0 { - case .inviteRequests(peers, requestsState.count): - return true - default: - return false - } - }) { - var updatedContexts = context.filter { c in - if case .inviteRequests = c { - return false - } else { - return true - } - } - updatedContexts.append(.inviteRequests(peers, requestsState.count)) - return updatedContexts.sorted() - } else { - return context - } - } else { - if let index = context.firstIndex(where: { - switch $0 { - case .inviteRequests: - return true - default: - return false - } - }) { - var updatedContexts = context - updatedContexts.remove(at: index) - return updatedContexts - } else { - return context - } - } - }) - .updatedSlowmodeState(slowmodeState) - }) - })) - } else if let inviteRequestsContext = strongSelf.inviteRequestsContext { - let _ = (inviteRequestsContext.state - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak inviteRequestsContext] state in - if state.count != inviteRequestsPending { - inviteRequestsContext?.loadMore() - } - }) - } - } - - if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage != pinnedMessage || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || voiceMessagesAvailableUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState || strongSelf.presentationInterfaceState.activeGroupCallInfo != activeGroupCallInfo || customEmojiAvailable != strongSelf.presentationInterfaceState.customEmojiAvailable || threadData != strongSelf.presentationInterfaceState.threadData || forumTopicData != strongSelf.presentationInterfaceState.forumTopicData || premiumGiftOptions != strongSelf.presentationInterfaceState.premiumGiftOptions { - strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in - return state - .updatedPinnedMessageId(pinnedMessageId) - .updatedActiveGroupCallInfo(activeGroupCallInfo) - .updatedPinnedMessage(pinnedMessage) - .updatedPeerIsBlocked(peerIsBlocked) - .updatedCallsAvailable(callsAvailable) - .updatedCallsPrivate(callsPrivate) - .updatedVoiceMessagesAvailable(voiceMessagesAvailable) - .updatedCustomEmojiAvailable(customEmojiAvailable) - .updatedThreadData(threadData) - .updatedForumTopicData(forumTopicData) - .updatedIsGeneralThreadClosed(forumTopicData?.isClosed) - .updatedPremiumGiftOptions(premiumGiftOptions) - .updatedTitlePanelContext({ context in - if pinnedMessageId != nil { - if !context.contains(where: { - switch $0 { - case .pinnedMessage: - return true - default: - return false - } - }) { - var updatedContexts = context - updatedContexts.append(.pinnedMessage) - return updatedContexts.sorted() - } else { - return context - } - } else { - if let index = context.firstIndex(where: { - switch $0 { - case .pinnedMessage: - return true - default: - return false - } - }) { - var updatedContexts = context - updatedContexts.remove(at: index) - return updatedContexts - } else { - return context - } - } - }) - .updatedSlowmodeState(slowmodeState) - }) - } - - if !strongSelf.didSetCachedDataReady { - strongSelf.didSetCachedDataReady = true - strongSelf.cachedDataReady.set(.single(true)) - } - } - }) - } else { - if !self.didSetCachedDataReady { - self.didSetCachedDataReady = true - self.cachedDataReady.set(.single(true)) - } - } } func loadDisplayNodeImpl() { @@ -2550,8 +634,6 @@ extension ChatControllerImpl { }) } - self.chatDisplayNode.overlayTitle = self.overlayTitle - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) |> map { peer in return SendAsPeer(peer: peer, subscribers: nil, isPremiumRequired: false) @@ -3542,10 +1624,10 @@ extension ChatControllerImpl { } }, reportSelectedMessages: { [weak self] in if let strongSelf = self, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { - if let (_, option, message) = strongSelf.presentationInterfaceState.reportReason { + if let reportReason = strongSelf.presentationInterfaceState.reportReason { let presentationData = strongSelf.presentationData strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }, completion: { _ in - let _ = (strongSelf.context.engine.messages.reportContent(subject: .messages(Array(messageIds)), option: option, message: message) + let _ = (strongSelf.context.engine.messages.reportContent(subject: .messages(Array(messageIds)), option: reportReason.option, message: reportReason.message) |> deliverOnMainQueue).startStandalone(completed: { strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .emoji(name: "PoliceCar", text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) }) @@ -4430,7 +2512,7 @@ extension ChatControllerImpl { } } - if let boostsToUnrestrict = (strongSelf.peerView?.cachedData as? CachedChannelData)?.boostsToUnrestrict, boostsToUnrestrict > 0, let bannedPermission, !bannedPermission.1 { + if let boostsToUnrestrict = (strongSelf.contentData?.state.peerView?.cachedData as? CachedChannelData)?.boostsToUnrestrict, boostsToUnrestrict > 0, let bannedPermission, !bannedPermission.1 { strongSelf.interfaceInteraction?.openBoostToUnrestrict() return } @@ -4766,7 +2848,7 @@ extension ChatControllerImpl { guard let strongSelf = self else { return } - strongSelf.scrolledToMessageIdValue = nil + strongSelf.contentData?.scrolledToMessageIdValue = nil })) } } @@ -4832,7 +2914,7 @@ extension ChatControllerImpl { if pinImmediately { pinAction(true, false) } else { - let topPinnedMessage: Signal = strongSelf.topPinnedMessageSignal(latest: true) + let topPinnedMessage: Signal = ChatControllerImpl.topPinnedMessageSignal(context: strongSelf.context, chatLocation: strongSelf.chatLocation, referenceMessage: nil) |> take(1) let _ = (topPinnedMessage @@ -5075,7 +3157,7 @@ extension ChatControllerImpl { return } - let topPinnedMessage: Signal = strongSelf.topPinnedMessageSignal(latest: true) + let topPinnedMessage: Signal = ChatControllerImpl.topPinnedMessageSignal(context: strongSelf.context, chatLocation: strongSelf.chatLocation, referenceMessage: nil) |> take(1) let _ = (topPinnedMessage @@ -5405,35 +3487,12 @@ extension ChatControllerImpl { strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInputMode({ _ in return .none }) }) } - }, reportPeerIrrelevantGeoLocation: { [weak self] in - guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else { - return - } - - strongSelf.chatDisplayNode.dismissInput() - - let actions = [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.ReportGroupLocation_Report, action: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.reportIrrelvantGeoDisposable = (strongSelf.context.engine.peers.reportPeer(peerId: peerId, reason: .irrelevantLocation, message: "") - |> deliverOnMainQueue).startStrict(completed: { [weak self] in - if let strongSelf = self { - strongSelf.reportIrrelvantGeoNoticePromise.set(.single(true)) - let _ = ApplicationSpecificNotice.setIrrelevantPeerGeoReport(engine: strongSelf.context.engine, peerId: peerId).startStandalone() - - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .emoji(name: "PoliceCar", text: strongSelf.presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) - } - }) - })] - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.ReportGroupLocation_Title, text: strongSelf.presentationData.strings.ReportGroupLocation_Text, actions: actions), in: .window(.root)) }, displaySlowmodeTooltip: { [weak self] sourceView, nodeRect in guard let strongSelf = self, let slowmodeState = strongSelf.presentationInterfaceState.slowmodeState else { return } - if let boostsToUnrestrict = (strongSelf.peerView?.cachedData as? CachedChannelData)?.boostsToUnrestrict, boostsToUnrestrict > 0 { + if let boostsToUnrestrict = (strongSelf.contentData?.state.peerView?.cachedData as? CachedChannelData)?.boostsToUnrestrict, boostsToUnrestrict > 0 { strongSelf.interfaceInteraction?.openBoostToUnrestrict() return } @@ -5636,7 +3695,7 @@ extension ChatControllerImpl { } }, openInviteRequests: { [weak self] in if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { - let controller = inviteRequestsController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id, existingContext: strongSelf.inviteRequestsContext) + let controller = inviteRequestsController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id, existingContext: strongSelf.contentData?.inviteRequestsContext) controller.navigationPresentation = .modal strongSelf.push(controller) } @@ -5976,7 +4035,7 @@ extension ChatControllerImpl { return } - guard let peerId = self.chatLocation.peerId, let cachedData = self.peerView?.cachedData as? CachedChannelData, let boostToUnrestrict = cachedData.boostsToUnrestrict else { + guard let peerId = self.chatLocation.peerId, let cachedData = self.contentData?.state.peerView?.cachedData as? CachedChannelData, let boostToUnrestrict = cachedData.boostsToUnrestrict else { return } @@ -6597,7 +4656,7 @@ extension ChatControllerImpl { let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId, quote: toSubject.quote.flatMap { quote in ChatInterfaceHighlightedState.Quote(string: quote.string, offset: quote.offset) }) controllerInteraction.highlightedState = highlightedState strongSelf.updateItemNodesHighlightedStates(animated: initial) - strongSelf.scrolledToMessageIdValue = ScrolledToMessageId(id: mappedId, allowedReplacementDirection: []) + strongSelf.contentData?.scrolledToMessageIdValue = ScrolledToMessageId(id: mappedId, allowedReplacementDirection: []) var hasQuote = false if let quote = toSubject.quote { @@ -6638,7 +4697,7 @@ extension ChatControllerImpl { guard let strongSelf = self else { return } - strongSelf.scrolledToMessageIdValue = nil + strongSelf.contentData?.scrolledToMessageIdValue = nil } self.chatDisplayNode.historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in @@ -6795,12 +4854,12 @@ extension ChatControllerImpl { //print("didScrollWithOffset offset: \(offset), itemNode: \(String(describing: itemNode))") if offset > 0.0 { - if var scrolledToMessageIdValue = strongSelf.scrolledToMessageIdValue { + if var scrolledToMessageIdValue = strongSelf.contentData?.scrolledToMessageIdValue { scrolledToMessageIdValue.allowedReplacementDirection.insert(.up) - strongSelf.scrolledToMessageIdValue = scrolledToMessageIdValue + strongSelf.contentData?.scrolledToMessageIdValue = scrolledToMessageIdValue } } else if offset < 0.0 { - strongSelf.scrolledToMessageIdValue = nil + strongSelf.contentData?.scrolledToMessageIdValue = nil } if let currentPinchSourceItemNode = strongSelf.currentPinchSourceItemNode { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift index bffd8e3466..332aa07e15 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift @@ -384,8 +384,15 @@ extension ChatControllerImpl { }) } case let .openChatInfo(expandAvatar, section): - let _ = self.presentVoiceMessageDiscardAlert(action: { - switch self.chatLocationInfoData { + let _ = self.presentVoiceMessageDiscardAlert(action: { [weak self] in + guard let self else { + return + } + guard let contentData = self.contentData else { + return + } + + switch contentData.chatLocationInfoData { case let .peer(peerView): self.navigationActionDisposable.set((peerView.get() |> take(1) @@ -413,7 +420,7 @@ extension ChatControllerImpl { default: mode = .generic } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: strongSelf.inviteRequestsContext) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: strongSelf.contentData?.inviteRequestsContext) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } } @@ -444,7 +451,7 @@ extension ChatControllerImpl { } } } else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, case let .replyThread(message) = self.chatLocation { - if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: channel, mode: .forumTopic(thread: message), avatarInitiallyExpanded: false, fromChat: true, requestsContext: self.inviteRequestsContext) { + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: channel, mode: .forumTopic(thread: message), avatarInitiallyExpanded: false, fromChat: true, requestsContext: self.contentData?.inviteRequestsContext) { self.effectiveNavigationController?.pushViewController(infoController) } } @@ -459,6 +466,10 @@ extension ChatControllerImpl { self.dismiss() } case .clearCache: + guard let contentData = self.contentData else { + return + } + let controller = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil)) self.present(controller, in: .window(.root)) @@ -470,7 +481,7 @@ extension ChatControllerImpl { self.clearCacheDisposable = disposable } - switch self.chatLocationInfoData { + switch contentData.chatLocationInfoData { case let .peer(peerView): self.navigationActionDisposable.set((peerView.get() |> take(1) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift index b7a20e66e7..0a0859db68 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift @@ -42,7 +42,7 @@ extension ChatControllerImpl { presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) } var peer = peer - if let peerDiscussionId = self.presentationInterfaceState.peerDiscussionId, let channel = self.peerView?.peers[peerDiscussionId] { + if let peerDiscussionId = self.presentationInterfaceState.peerDiscussionId, let channel = self.contentData?.state.peerView?.peers[peerDiscussionId] { peer = EnginePeer(channel) } let controller = chatMessagePaymentAlertController( diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift index b070331813..f06530bba1 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift @@ -168,7 +168,7 @@ extension ChatControllerImpl { } var canResetWallpaper = false - if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData { + if let cachedUserData = strongSelf.contentData?.state.peerView?.cachedData as? CachedUserData { canResetWallpaper = cachedUserData.wallpaper != nil } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 92b0fbed73..8dd7ae9ec8 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -137,11 +137,6 @@ import TelegramCallsUI import QuickShareScreen import PostSuggestionsSettingsScreen -public enum ChatControllerPeekActions { - case standard - case remove(() -> Void) -} - public final class ChatControllerOverlayPresentationData { public let expandData: (ASDisplayNode?, () -> Void) public init(expandData: (ASDisplayNode?, () -> Void)) { @@ -243,31 +238,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let chatNavigationStack: [ChatNavigationStackItem] let customChatNavigationStack: [EnginePeer.Id]? - public var peekActions: ChatControllerPeekActions = .standard - var didSetup3dTouch: Bool = false + var didSetupDropToPaste: Bool = false let context: AccountContext public internal(set) var chatLocation: ChatLocation public let subject: ChatControllerSubject? + var botStart: ChatControllerInitialBotStart? var attachBotStart: ChatControllerInitialAttachBotStart? var botAppStart: ChatControllerInitialBotAppStart? var mode: ChatControllerPresentationMode - let peerDisposable = MetaDisposable() - let titleDisposable = MetaDisposable() + var pendingContentData: (contentData: ChatControllerImpl.ContentData, historyNode: ChatHistoryListNodeImpl)? + var contentData: ChatControllerImpl.ContentData? + let contentDataReady = ValuePromise(false, ignoreRepeated: true) + var contentDataDisposable: Disposable? + var didHandlePerformDismissAction: Bool = false + var accountPeerDisposable: Disposable? - let navigationActionDisposable = MetaDisposable() - var networkStateDisposable: Disposable? - - let messageIndexDisposable = MetaDisposable() - - let _chatLocationInfoReady = Promise() - var didSetChatLocationInfoReady = false - var chatLocationInfoData: ChatLocationInfoData let cachedDataReady = Promise() var didSetCachedDataReady = false + + let navigationActionDisposable = MetaDisposable() + let messageIndexDisposable = MetaDisposable() + var networkStateDisposable: Disposable? let wallpaperReady = Promise() let presentationReady = Promise() @@ -294,9 +289,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var moreBarButton: MoreHeaderButton var moreInfoNavigationButton: ChatNavigationButton? - var peerView: PeerView? - var threadInfo: EngineMessageHistoryThread.Info? - var historyStateDisposable: Disposable? let galleryHiddenMesageAndMediaDisposable = MetaDisposable() @@ -343,14 +335,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var stateServiceTasks: [AnyHashable: Disposable] = [:] - var preloadHistoryPeerId: PeerId? - let preloadHistoryPeerIdDisposable = MetaDisposable() - - var preloadNextChatPeerId: PeerId? - let preloadNextChatPeerIdDisposable = MetaDisposable() - - var preloadSavedMessagesChatsDisposable: Disposable? - let botCallbackAlertMessage = Promise(nil) var botCallbackAlertMessageDisposable: Disposable? @@ -381,8 +365,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var recorderDataDisposable = MetaDisposable() - var buttonKeyboardMessageDisposable: Disposable? - var cachedDataDisposable: Disposable? var chatUnreadCountDisposable: Disposable? var buttonUnreadCountDisposable: Disposable? var chatUnreadMentionCountDisposable: Disposable? @@ -411,8 +393,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G public let canReadHistory = ValuePromise(true, ignoreRepeated: true) public let hasBrowserOrAppInFront = Promise(false) - var reminderActivity: NSUserActivity? - var isReminderActivityEnabled: Bool = false var canReadHistoryValue = false { didSet { @@ -505,13 +485,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G weak var currentPinchSourceItemNode: ListViewItemNode? var screenCaptureManager: ScreenCaptureDetectionManager? - let chatAdditionalDataDisposable = MetaDisposable() - - var reportIrrelvantGeoNoticePromise = Promise() - var reportIrrelvantGeoNotice: Bool? - var reportIrrelvantGeoDisposable: Disposable? - - var hasScheduledMessages: Bool = false var volumeButtonsListener: VolumeButtonsListener? @@ -536,7 +509,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let selectAddMemberDisposable = MetaDisposable() let addMemberDisposable = MetaDisposable() let joinChannelDisposable = MetaDisposable() - var premiumOrStarsRequiredDisposable: Disposable? var shouldDisplayDownButton = false @@ -583,40 +555,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G public var isSelectingMessagesUpdated: ((Bool) -> Void)? - let scrolledToMessageId = ValuePromise(nil, ignoreRepeated: true) - var scrolledToMessageIdValue: ScrolledToMessageId? = nil { - didSet { - self.scrolledToMessageId.set(self.scrolledToMessageIdValue) - } - } - var translationStateDisposable: Disposable? var premiumGiftSuggestionDisposable: Disposable? - var nextChannelToReadDisposable: Disposable? - var offerNextChannelToRead = false - - var inviteRequestsContext: PeerInvitationImportersContext? - var inviteRequestsDisposable = MetaDisposable() - - var overlayTitle: String? { - var title: String? - if let threadInfo = self.threadInfo { - title = threadInfo.title - } else if let peerView = self.peerView { - if let peer = peerViewMainPeer(peerView) { - title = EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) - } - } - return title - } - var currentSpeechHolder: SpeechSynthesizerHolder? var powerSavingMonitoringDisposable: Disposable? var avatarNode: ChatAvatarNavigationNode? - var storyStats: PeerStoryStats? var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)? var performOpenURL: ((Message?, String, Promise?) -> Void)? @@ -728,27 +674,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G default: groupCallPanelSource = .none } - self.chatLocationInfoData = .peer(Promise()) - case let .replyThread(replyThreadMessage): + case .replyThread: locationBroadcastPanelSource = .none groupCallPanelSource = .none - let promise = Promise() - if let effectiveMessageId = replyThreadMessage.effectiveMessageId { - promise.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: effectiveMessageId)) - |> map { message -> Message? in - guard let message = message else { - return nil - } - return message._asMessage() - }) - } else { - promise.set(.single(nil)) - } - self.chatLocationInfoData = .replyThread(promise) case .customChatContents: locationBroadcastPanelSource = .none groupCallPanelSource = .none - self.chatLocationInfoData = .customChatContents } var presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -1325,7 +1256,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { if let file = message.media.first(where: { $0 is TelegramMediaFile}) as? TelegramMediaFile, file.isVideo && !file.isAnimated { - self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: true, fullscreen: false) + self.chatDisplayNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: true, fullscreen: false) } else { self.controllerInteraction?.activateAdAction(message.id, nil, true, false) return true @@ -1495,7 +1426,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { - legacyMediaEditor(context: self.context, peer: peer, threadTitle: self.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { + legacyMediaEditor(context: self.context, peer: peer, threadTitle: self.contentData?.state.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { transitionCompletion() }, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView(isFile: false) @@ -2682,7 +2613,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let message, let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { - strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: false, fullscreen: false) + strongSelf.chatDisplayNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: false, fullscreen: false) } if let performOpenURL = strongSelf.performOpenURL { @@ -2867,7 +2798,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData, cachedUserData.callsPrivate { + if let cachedUserData = strongSelf.contentData?.state.peerView?.cachedData as? CachedUserData, cachedUserData.callsPrivate { let presentationData = context.sharedContext.currentPresentationData.with { $0 } strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) @@ -3887,7 +3818,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in + legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.contentData?.state.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView(isFile: true) }, sendMessagesWithSignals: { [weak self] signals, _, _, _ in if let strongSelf = self { @@ -4051,7 +3982,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: media, fullscreen: fullscreen) + self.chatDisplayNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: media, fullscreen: fullscreen) self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: adAttribute.url, concealed: false, external: true, progress: progress)) }, adContextAction: { [weak self] message, sourceNode, gesture in guard let self else { @@ -4642,7 +4573,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let premiumOptions = giftOptions.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } var hasBirthday = false - if let cachedUserData = self.peerView?.cachedData as? CachedUserData { + if let cachedUserData = self.contentData?.state.peerView?.cachedData as? CachedUserData { hasBirthday = hasBirthdayToday(cachedData: cachedUserData) } let controller = self.context.sharedContext.makeGiftOptionsController(context: context, peerId: peerId, premiumOptions: premiumOptions, hasBirthday: hasBirthday, completion: nil) @@ -4862,7 +4793,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.navigationItem.titleView = self.chatTitleView self.chatTitleView?.longPressed = { [weak self] in - if let strongSelf = self, let peerView = strongSelf.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible { + if let strongSelf = self, let peerView = strongSelf.contentData?.state.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible { if case .standard(.previewing) = strongSelf.mode { } else { strongSelf.interfaceInteraction?.beginMessageSearch(.everything, "") @@ -5299,7 +5230,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - self.reloadChatLocation(chatLocation: self.chatLocation, isReady: nil) + self.reloadChatLocation(chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, historyNode: self.chatDisplayNode.historyNode, isReady: nil) self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get() |> deliverOnMainQueue).startStrict(next: { [weak self] message in @@ -5782,8 +5713,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self, strongSelf.canReadHistoryValue != value { strongSelf.canReadHistoryValue = value strongSelf.raiseToListen?.enabled = value - strongSelf.isReminderActivityEnabled = value - strongSelf.updateReminderActivity() } }) @@ -5808,104 +5737,89 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = ChatControllerCount.modify { value in return value - 1 } - - let deallocate: () -> Void = { - self.historyStateDisposable?.dispose() - self.messageIndexDisposable.dispose() - self.navigationActionDisposable.dispose() - self.galleryHiddenMesageAndMediaDisposable.dispose() - self.temporaryHiddenGalleryMediaDisposable.dispose() - self.peerDisposable.dispose() - self.accountPeerDisposable?.dispose() - self.titleDisposable.dispose() - self.messageContextDisposable.dispose() - self.controllerNavigationDisposable.dispose() - self.sentMessageEventsDisposable.dispose() - self.failedMessageEventsDisposable.dispose() - self.sentPeerMediaMessageEventsDisposable.dispose() - self.messageActionCallbackDisposable.dispose() - self.messageActionUrlAuthDisposable.dispose() - self.editMessageDisposable.dispose() - self.editMessageErrorsDisposable.dispose() - self.enqueueMediaMessageDisposable.dispose() - self.resolvePeerByNameDisposable?.dispose() - self.shareStatusDisposable?.dispose() - self.clearCacheDisposable?.dispose() - self.bankCardDisposable?.dispose() - self.botCallbackAlertMessageDisposable?.dispose() - self.selectMessagePollOptionDisposables?.dispose() - for (_, info) in self.contextQueryStates { - info.1.dispose() - } - self.urlPreviewQueryState?.1.dispose() - self.editingUrlPreviewQueryState?.1.dispose() - self.replyMessageState?.1.dispose() - self.audioRecorderDisposable?.dispose() - self.audioRecorderStatusDisposable?.dispose() - self.videoRecorderDisposable?.dispose() - self.buttonKeyboardMessageDisposable?.dispose() - self.cachedDataDisposable?.dispose() - self.resolveUrlDisposable?.dispose() - self.chatUnreadCountDisposable?.dispose() - self.buttonUnreadCountDisposable?.dispose() - self.chatUnreadMentionCountDisposable?.dispose() - self.peerInputActivitiesDisposable?.dispose() - self.interactiveEmojiSyncDisposable.dispose() - self.recentlyUsedInlineBotsDisposable?.dispose() - self.unpinMessageDisposable?.dispose() - self.inputActivityDisposable?.dispose() - self.recordingActivityDisposable?.dispose() - self.acquiredRecordingActivityDisposable?.dispose() - self.presentationDataDisposable?.dispose() - self.searchDisposable?.dispose() - self.applicationInForegroundDisposable?.dispose() - self.applicationInFocusDisposable?.dispose() - self.canReadHistoryDisposable?.dispose() - self.networkStateDisposable?.dispose() - self.chatAdditionalDataDisposable.dispose() - self.shareStatusDisposable?.dispose() - self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeTarget(self) - self.preloadHistoryPeerIdDisposable.dispose() - self.preloadNextChatPeerIdDisposable.dispose() - self.reportIrrelvantGeoDisposable?.dispose() - self.reminderActivity?.invalidate() - self.updateSlowmodeStatusDisposable.dispose() - self.keepPeerInfoScreenDataHotDisposable.dispose() - self.preloadAvatarDisposable.dispose() - self.peekTimerDisposable.dispose() - self.hasActiveGroupCallDisposable?.dispose() - self.createVoiceChatDisposable.dispose() - self.checksTooltipDisposable.dispose() - self.peerSuggestionsDisposable.dispose() - self.peerSuggestionsDismissDisposable.dispose() - self.selectAddMemberDisposable.dispose() - self.addMemberDisposable.dispose() - self.joinChannelDisposable.dispose() - self.nextChannelToReadDisposable?.dispose() - self.inviteRequestsDisposable.dispose() - self.sendAsPeersDisposable?.dispose() - self.preloadAttachBotIconsDisposables?.dispose() - self.keepMessageCountersSyncrhonizedDisposable?.dispose() - self.keepSavedMessagesSyncrhonizedDisposable?.dispose() - self.translationStateDisposable?.dispose() - self.premiumGiftSuggestionDisposable?.dispose() - self.powerSavingMonitoringDisposable?.dispose() - self.saveMediaDisposable?.dispose() - self.giveawayStatusDisposable?.dispose() - self.nameColorDisposable?.dispose() - self.choosingStickerActivityDisposable?.dispose() - self.automaticMediaDownloadSettingsDisposable?.dispose() - self.stickerSettingsDisposable?.dispose() - self.searchQuerySuggestionState?.1.dispose() - self.preloadSavedMessagesChatsDisposable?.dispose() - self.recorderDataDisposable.dispose() - self.displaySendWhenOnlineTipDisposable.dispose() - self.networkSpeedEventsDisposable?.dispose() - self.postedScheduledMessagesEventsDisposable?.dispose() - self.premiumOrStarsRequiredDisposable?.dispose() - self.updateChatLocationThreadDisposable?.dispose() + + self.historyStateDisposable?.dispose() + self.messageIndexDisposable.dispose() + self.navigationActionDisposable.dispose() + self.galleryHiddenMesageAndMediaDisposable.dispose() + self.temporaryHiddenGalleryMediaDisposable.dispose() + self.messageContextDisposable.dispose() + self.controllerNavigationDisposable.dispose() + self.sentMessageEventsDisposable.dispose() + self.failedMessageEventsDisposable.dispose() + self.sentPeerMediaMessageEventsDisposable.dispose() + self.messageActionCallbackDisposable.dispose() + self.messageActionUrlAuthDisposable.dispose() + self.editMessageDisposable.dispose() + self.editMessageErrorsDisposable.dispose() + self.enqueueMediaMessageDisposable.dispose() + self.resolvePeerByNameDisposable?.dispose() + self.shareStatusDisposable?.dispose() + self.clearCacheDisposable?.dispose() + self.bankCardDisposable?.dispose() + self.botCallbackAlertMessageDisposable?.dispose() + self.selectMessagePollOptionDisposables?.dispose() + for (_, info) in self.contextQueryStates { + info.1.dispose() } - deallocate() + self.urlPreviewQueryState?.1.dispose() + self.editingUrlPreviewQueryState?.1.dispose() + self.replyMessageState?.1.dispose() + self.audioRecorderDisposable?.dispose() + self.audioRecorderStatusDisposable?.dispose() + self.videoRecorderDisposable?.dispose() + self.resolveUrlDisposable?.dispose() + self.chatUnreadCountDisposable?.dispose() + self.buttonUnreadCountDisposable?.dispose() + self.chatUnreadMentionCountDisposable?.dispose() + self.peerInputActivitiesDisposable?.dispose() + self.interactiveEmojiSyncDisposable.dispose() + self.recentlyUsedInlineBotsDisposable?.dispose() + self.unpinMessageDisposable?.dispose() + self.inputActivityDisposable?.dispose() + self.recordingActivityDisposable?.dispose() + self.acquiredRecordingActivityDisposable?.dispose() + self.presentationDataDisposable?.dispose() + self.searchDisposable?.dispose() + self.applicationInForegroundDisposable?.dispose() + self.applicationInFocusDisposable?.dispose() + self.canReadHistoryDisposable?.dispose() + self.networkStateDisposable?.dispose() + self.shareStatusDisposable?.dispose() + self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeTarget(self) + self.updateSlowmodeStatusDisposable.dispose() + self.keepPeerInfoScreenDataHotDisposable.dispose() + self.preloadAvatarDisposable.dispose() + self.peekTimerDisposable.dispose() + self.hasActiveGroupCallDisposable?.dispose() + self.createVoiceChatDisposable.dispose() + self.checksTooltipDisposable.dispose() + self.peerSuggestionsDisposable.dispose() + self.peerSuggestionsDismissDisposable.dispose() + self.selectAddMemberDisposable.dispose() + self.addMemberDisposable.dispose() + self.joinChannelDisposable.dispose() + self.sendAsPeersDisposable?.dispose() + self.preloadAttachBotIconsDisposables?.dispose() + self.keepMessageCountersSyncrhonizedDisposable?.dispose() + self.keepSavedMessagesSyncrhonizedDisposable?.dispose() + self.translationStateDisposable?.dispose() + self.premiumGiftSuggestionDisposable?.dispose() + self.powerSavingMonitoringDisposable?.dispose() + self.saveMediaDisposable?.dispose() + self.giveawayStatusDisposable?.dispose() + self.nameColorDisposable?.dispose() + self.choosingStickerActivityDisposable?.dispose() + self.automaticMediaDownloadSettingsDisposable?.dispose() + self.stickerSettingsDisposable?.dispose() + self.searchQuerySuggestionState?.1.dispose() + self.recorderDataDisposable.dispose() + self.displaySendWhenOnlineTipDisposable.dispose() + self.networkSpeedEventsDisposable?.dispose() + self.postedScheduledMessagesEventsDisposable?.dispose() + self.updateChatLocationThreadDisposable?.dispose() + self.accountPeerDisposable?.dispose() + self.contentDataDisposable?.dispose() } public func updatePresentationMode(_ mode: ChatControllerPresentationMode) { @@ -6039,10 +5953,48 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatTitleView?.updateThemeAndStrings(theme: presentationTheme, strings: self.presentationData.strings, hasEmbeddedTitleContent: self.hasEmbeddedTitleContent) } - func topPinnedMessageSignal(latest: Bool) -> Signal { - var pinnedPeerId: EnginePeer.Id? - let threadId = self.chatLocation.threadId - let loadState: Signal = self.chatDisplayNode.historyNode.historyState.get() + enum PinnedReferenceMessage { + struct Loaded { + var id: MessageId + var minId: MessageId + var isScrolled: Bool + } + + case ready(Loaded) + case loading + } + + static func topPinnedScrollReferenceMessage(historyNode: ChatHistoryListNodeImpl, scrolledToMessageId: Signal) -> Signal { + return combineLatest(queue: Queue.mainQueue(), + scrolledToMessageId, + historyNode.topVisibleMessageRange.get() + ) + |> map { scrolledToMessageId, topVisibleMessageRange -> PinnedReferenceMessage? in + if let topVisibleMessageRange, topVisibleMessageRange.isLoading { + return .loading + } + + let bottomVisibleMessage = topVisibleMessageRange?.lowerBound.id + let topVisibleMessage = topVisibleMessageRange?.upperBound.id + + if let scrolledToMessageId = scrolledToMessageId { + if let topVisibleMessage, let bottomVisibleMessage { + if scrolledToMessageId.allowedReplacementDirection.contains(.up) && topVisibleMessage < scrolledToMessageId.id { + return .ready(PinnedReferenceMessage.Loaded(id: topVisibleMessage, minId: bottomVisibleMessage, isScrolled: false)) + } + } + return .ready(PinnedReferenceMessage.Loaded(id: scrolledToMessageId.id, minId: scrolledToMessageId.id, isScrolled: true)) + } else if let topVisibleMessage, let bottomVisibleMessage { + return .ready(PinnedReferenceMessage.Loaded(id: topVisibleMessage, minId: bottomVisibleMessage, isScrolled: false)) + } else { + return nil + } + } + } + + static func topPinnedScrollMessage(context: AccountContext, chatLocation: ChatLocation, historyNode: ChatHistoryListNodeImpl, scrolledToMessageId: Signal) -> Signal { + //TODO:release move to ContentData + let loadState: Signal = historyNode.historyState.get() |> map { state -> Bool in switch state { case .loading: @@ -6053,12 +6005,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } |> distinctUntilChanged - switch self.chatLocation { + let referenceMessage = self.topPinnedScrollReferenceMessage(historyNode: historyNode, scrolledToMessageId: scrolledToMessageId) + + return loadState + |> mapToSignal { loadState in + if !loadState { + return .single(nil) + } else { + return ChatControllerImpl.topPinnedMessageSignal(context: context, chatLocation: chatLocation, referenceMessage: referenceMessage) + } + } + } + + static func topPinnedMessageSignal(context: AccountContext, chatLocation: ChatLocation, referenceMessage: Signal?) -> Signal { + var pinnedPeerId: EnginePeer.Id? + let threadId = chatLocation.threadId + + switch chatLocation { case let .peer(id): pinnedPeerId = id case let .replyThread(message): if message.isForumPost { - pinnedPeerId = self.chatLocation.peerId + pinnedPeerId = chatLocation.peerId } default: break @@ -6067,51 +6035,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let peerId = pinnedPeerId { let topPinnedMessage: Signal - enum ReferenceMessage { - struct Loaded { - var id: MessageId - var minId: MessageId - var isScrolled: Bool - } - - case ready(Loaded) - case loading - } - - let referenceMessage: Signal - if latest { - referenceMessage = .single(nil) - } else { - referenceMessage = combineLatest( - queue: Queue.mainQueue(), - self.scrolledToMessageId.get(), - self.chatDisplayNode.historyNode.topVisibleMessageRange.get() - ) - |> map { scrolledToMessageId, topVisibleMessageRange -> ReferenceMessage? in - if let topVisibleMessageRange = topVisibleMessageRange, topVisibleMessageRange.isLoading { - return .loading - } - - let bottomVisibleMessage = topVisibleMessageRange?.lowerBound.id - let topVisibleMessage = topVisibleMessageRange?.upperBound.id - - if let scrolledToMessageId = scrolledToMessageId { - if let topVisibleMessage, let bottomVisibleMessage { - if scrolledToMessageId.allowedReplacementDirection.contains(.up) && topVisibleMessage < scrolledToMessageId.id { - return .ready(ReferenceMessage.Loaded(id: topVisibleMessage, minId: bottomVisibleMessage, isScrolled: false)) - } - } - return .ready(ReferenceMessage.Loaded(id: scrolledToMessageId.id, minId: scrolledToMessageId.id, isScrolled: true)) - } else if let topVisibleMessage, let bottomVisibleMessage { - return .ready(ReferenceMessage.Loaded(id: topVisibleMessage, minId: bottomVisibleMessage, isScrolled: false)) - } else { - return nil - } - } - } - - let context = self.context - func pinnedHistorySignal(anchorMessageId: MessageId?, count: Int) -> Signal { let location: ChatHistoryLocation if let anchorMessageId = anchorMessageId { @@ -6187,36 +6110,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let adjustedReplyHistory: Signal - if latest { - adjustedReplyHistory = pinnedHistorySignal(anchorMessageId: nil, count: loadCount) - |> map { view -> PinnedHistory in - switch view { - case .Loading: - return PinnedHistory(messages: [], totalCount: 0) - case let .HistoryView(viewValue, _, _, _, _, _, _): - var messages: [PinnedHistory.PinnedMessage] = [] - var totalCount = viewValue.entries.count - for i in 0 ..< viewValue.entries.count { - let index: Int - if !viewValue.holeEarlier && viewValue.earlierId == nil { - index = i - } else if let location = viewValue.entries[i].location { - index = location.index - totalCount = location.count - } else { - index = i - } - messages.append(PinnedHistory.PinnedMessage( - message: viewValue.entries[i].message, - index: index - )) - } - return PinnedHistory(messages: messages, totalCount: totalCount) - } - } - } else { + if let referenceMessage { adjustedReplyHistory = (Signal { subscriber in - var referenceMessageValue: ReferenceMessage? + var referenceMessageValue: PinnedReferenceMessage? var view: ChatHistoryViewUpdate? let updateState: () -> Void = { @@ -6298,19 +6194,41 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } |> runOn(.mainQueue())) |> restart + } else { + adjustedReplyHistory = pinnedHistorySignal(anchorMessageId: nil, count: loadCount) + |> map { view -> PinnedHistory in + switch view { + case .Loading: + return PinnedHistory(messages: [], totalCount: 0) + case let .HistoryView(viewValue, _, _, _, _, _, _): + var messages: [PinnedHistory.PinnedMessage] = [] + var totalCount = viewValue.entries.count + for i in 0 ..< viewValue.entries.count { + let index: Int + if !viewValue.holeEarlier && viewValue.earlierId == nil { + index = i + } else if let location = viewValue.entries[i].location { + index = location.index + totalCount = location.count + } else { + index = i + } + messages.append(PinnedHistory.PinnedMessage( + message: viewValue.entries[i].message, + index: index + )) + } + return PinnedHistory(messages: messages, totalCount: totalCount) + } + } } topPinnedMessage = combineLatest(queue: .mainQueue(), adjustedReplyHistory, topMessage, - referenceMessage, - loadState + referenceMessage ?? .single(nil) ) - |> map { pinnedMessages, topMessage, referenceMessage, loadState -> ChatPinnedMessage? in - if !loadState { - return nil - } - + |> map { pinnedMessages, topMessage, referenceMessage -> ChatPinnedMessage? in var message: ChatPinnedMessage? let topMessageId: MessageId @@ -6584,12 +6502,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - if !self.didSetup3dTouch { - self.didSetup3dTouch = true - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - let dropInteraction = UIDropInteraction(delegate: self) - self.chatDisplayNode.view.addInteraction(dropInteraction) - } + if !self.didSetupDropToPaste { + self.didSetupDropToPaste = true + let dropInteraction = UIDropInteraction(delegate: self) + self.chatDisplayNode.view.addInteraction(dropInteraction) } if !self.checkedPeerChatServiceActions { @@ -7224,7 +7140,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let button = self.rightNavigationButton { if case .standard(.previewing) = self.mode { self.navigationButtonAction(button.action) - } else if case let .peer(peerId) = self.chatLocation, case .openChatInfo(expandAvatar: true, _) = button.action, let storyStats = self.storyStats, storyStats.unseenCount != 0, let avatarNode = self.avatarNode { + } else if case let .peer(peerId) = self.chatLocation, case .openChatInfo(expandAvatar: true, _) = button.action, let storyStats = self.contentData?.state.storyStats, storyStats.unseenCount != 0, let avatarNode = self.avatarNode { self.openStories(peerId: peerId, avatarHeaderNode: nil, avatarNode: avatarNode.avatarNode) } else { self.navigationButtonAction(button.action) @@ -8729,7 +8645,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self else { return } - let disposable = openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in + let disposable = openUserGeneratedUrl(context: self.context, peerId: self.contentData?.state.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in self?.present(c, in: .window(.root)) }, openResolved: { [weak self] resolved in self?.openResolved(result: resolved, sourceMessageId: message?.id, progress: progress, forceExternal: forceExternal, concealed: concealed, commit: commit) @@ -8809,7 +8725,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } public func beginReportSelection(reason: NavigateToChatControllerParams.ReportReason) { - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedReportReason((reason.title, reason.option, reason.message)).updatedInterfaceState { $0.withUpdatedSelectedMessages([]) } }) + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedReportReason(ChatPresentationInterfaceState.ReportReasonData(title: reason.title, option: reason.option, message: reason.message)).updatedInterfaceState { $0.withUpdatedSelectedMessages([]) } }) } func displayMediaRecordingTooltip() { @@ -8987,7 +8903,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else { return } - guard let peerId = self.chatLocation.peerId, let emojiPack = (self.peerView?.cachedData as? CachedChannelData)?.emojiPack, let thumbnailFileId = emojiPack.thumbnailFileId else { + guard let peerId = self.chatLocation.peerId, let emojiPack = (self.contentData?.state.peerView?.cachedData as? CachedChannelData)?.emojiPack, let thumbnailFileId = emojiPack.thumbnailFileId else { return } @@ -9042,7 +8958,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard !self.didDisplayBirthdayTooltip else { return } - if let birthday = (self.peerView?.cachedData as? CachedUserData)?.birthday { + if let birthday = (self.contentData?.state.peerView?.cachedData as? CachedUserData)?.birthday { PeerInfoScreenImpl.preloadBirthdayAnimations(context: self.context, birthday: birthday) } guard let rect = self.chatDisplayNode.frameForGiftButton(), self.effectiveNavigationController?.topViewController === self, let peer = self.presentationInterfaceState.renderedPeer?.peer.flatMap({ EnginePeer($0) }) else { @@ -9286,34 +9202,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - func updateReminderActivity() { - if self.isReminderActivityEnabled && false { - if #available(iOS 9.0, *) { - if self.reminderActivity == nil, case let .peer(peerId) = self.chatLocation, let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer { - let reminderActivity = NSUserActivity(activityType: "RemindAboutChatIntent") - self.reminderActivity = reminderActivity - if peer is TelegramGroup { - reminderActivity.title = self.presentationData.strings.Activity_RemindAboutGroup(EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string - } else if let channel = peer as? TelegramChannel { - if case .broadcast = channel.info { - reminderActivity.title = self.presentationData.strings.Activity_RemindAboutChannel(EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string - } else { - reminderActivity.title = self.presentationData.strings.Activity_RemindAboutGroup(EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string - } - } else { - reminderActivity.title = self.presentationData.strings.Activity_RemindAboutUser(EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string - } - reminderActivity.userInfo = ["peerId": peerId.toInt64(), "peerTitle": EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)] - reminderActivity.isEligibleForHandoff = true - reminderActivity.becomeCurrent() - } - } - } else if let reminderActivity = self.reminderActivity { - self.reminderActivity = nil - reminderActivity.invalidate() - } - } - func updateSlowmodeStatus() { if let slowmodeState = self.presentationInterfaceState.slowmodeState, case let .timestamp(slowmodeActiveUntilTimestamp) = slowmodeState.variant { let timestamp = Int32(Date().timeIntervalSince1970) @@ -9783,7 +9671,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func updateNextChannelToReadVisibility() { - self.chatDisplayNode.historyNode.offerNextChannelToRead = self.offerNextChannelToRead && self.presentationInterfaceState.interfaceState.selectionState == nil + guard let contentData = self.contentData else { + return + } + self.chatDisplayNode.historyNode.offerNextChannelToRead = contentData.state.offerNextChannelToRead && self.presentationInterfaceState.interfaceState.selectionState == nil } func displayGiveawayStatusInfo(messageId: EngineMessage.Id, giveawayInfo: PremiumGiveawayInfo) { @@ -9820,7 +9711,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var currentChatSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection? public func updateChatLocationThread(threadId: Int64?, animationDirection: ChatControllerAnimateInnerChatSwitchDirection? = nil) { - if self.isUpdatingChatLocationThread { + /*if self.isUpdatingChatLocationThread { return } @@ -9836,6 +9727,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let navigationSnapshot = self.chatTitleView?.prepareSnapshotState() + //let historyNode = self.chatDisplayNode.createHistoryNodeForChatLocation(chatLocation: self.chatLocation, chatLocationContextHolder: ) + let rightBarButtonItemSnapshots: [(UIView, CGRect)] = (self.navigationItem.rightBarButtonItems ?? []).compactMap { item -> (UIView, CGRect)? in guard let view = item.customDisplayNode?.view, let snapshotView = view.snapshotView(afterScreenUpdates: false) else { return nil @@ -9870,7 +9763,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let isReady = Promise() - self.reloadChatLocation(chatLocation: updatedChatLocation, isReady: isReady) + let chatLocationContextHolder = Atomic(value: nil) + self.reloadChatLocation(chatLocation: updatedChatLocation, chatLocationContextHolder: chatLocationContextHolder, historyNode: historyNode, isReady: isReady) self.isUpdatingChatLocationThread = true self.updateChatLocationThreadDisposable?.dispose() @@ -9928,7 +9822,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.currentChatSwitchDirection = nil - }) + })*/ } public var contentContainerNode: ASDisplayNode { diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift new file mode 100644 index 0000000000..48046d104a --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -0,0 +1,2198 @@ +import Foundation +import TelegramPresentationData +import AccountContext +import Postbox +import ChatInterfaceState +import TelegramCore +import SwiftSignalKit +import ChatTitleView +import AvatarNode +import ChatPresentationInterfaceState +import PeerInfoScreen +import TelegramNotices +import ChatListUI +import EmojiStatusComponent +import TelegramUIPreferences +import TranslateUI + +extension ChatControllerImpl { + final class ContentData { + final class Configuration: Equatable { + let subject: ChatControllerSubject? + let selectionState: ChatInterfaceSelectionState? + let reportReason: ChatPresentationInterfaceState.ReportReasonData? + + init( + subject: ChatControllerSubject?, + selectionState: ChatInterfaceSelectionState?, + reportReason: ChatPresentationInterfaceState.ReportReasonData? + ) { + self.subject = subject + self.selectionState = selectionState + self.reportReason = reportReason + } + + static func ==(lhs: Configuration, rhs: Configuration) -> Bool { + if lhs.subject != rhs.subject { + return false + } + if lhs.selectionState != rhs.selectionState { + return false + } + if lhs.reportReason != rhs.reportReason { + return false + } + return true + } + } + + enum InfoAvatar { + case peer(peer: EnginePeer, imageOverride: AvatarNodeImageOverride?, contextActionIsEnabled: Bool, accessibilityLabel: String?) + case emojiStatus(content: EmojiStatusComponent.Content, contextActionIsEnabled: Bool) + } + + enum PerformDismissAction { + case upgraded(EnginePeer.Id) + case movedToForumTopics + case dismiss + } + + struct State { + var peerView: PeerView? + var threadInfo: EngineMessageHistoryThread.Info? + var infoAvatar: InfoAvatar? + var navigationUserInfo: PeerInfoNavigationSourceTag? + var chatTitleContent: ChatTitleContent? + var storyStats: PeerStoryStats? + var renderedPeer: RenderedPeer? + var hasScheduledMessages: Bool = false + var hasSearchTags: Bool = false + var hasSavedChats: Bool = false + var isPremiumRequiredForMessaging: Bool = false + var contactStatus: ChatContactStatus? + var adMessage: Message? + var offerNextChannelToRead: Bool = false + var nextChannelToRead: (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation)? + var nextChannelToReadDisplayName: Bool = false + var isNotAccessible: Bool = false + var hasBots: Bool = false + var hasBotCommands: Bool = false + var botMenuButton: BotMenuButton = .commands + var isArchived: Bool = false + var peerIsMuted: Bool = false + var peerDiscussionId: EnginePeer.Id? + var peerGeoLocation: PeerGeoLocation? + var explicitelyCanPinMessages: Bool = false + var autoremoveTimeout: Int32? + var currentSendAsPeerId: EnginePeer.Id? + var copyProtectionEnabled: Bool = false + var sendPaidMessageStars: StarsAmount? + var alwaysShowGiftButton: Bool = false + var disallowedGifts: TelegramDisallowedGifts? + var appliedBoosts: Int32? + var boostsToUnrestrict: Int32? + var hasBirthdayToday: Bool = false + var businessIntro: TelegramBusinessIntro? + var peerVerification: PeerVerification? + var starGiftsAvailable: Bool = false + var performDismissAction: PerformDismissAction? + var savedMessagesTopicPeer: EnginePeer? + + var keyboardButtonsMessage: Message? + var pinnedMessageId: EngineMessage.Id? + var pinnedMessage: ChatPinnedMessage? + var peerIsBlocked: Bool = false + var callsAvailable: Bool = true + var callsPrivate: Bool = false + var activeGroupCallInfo: ChatActiveGroupCallInfo? + var slowmodeState: ChatSlowmodeState? + + var suggestPremiumGift: Bool = false + var translationState: ChatPresentationTranslationState? + var voiceMessagesAvailable: Bool = true + var requestsState: PeerInvitationImportersState? + var dismissedInvitationRequests: [Int64]? + + var customEmojiAvailable: Bool = false + var threadData: ChatPresentationInterfaceState.ThreadData? + var forumTopicData: ChatPresentationInterfaceState.ThreadData? + var isGeneralThreadClosed: Bool? + var premiumGiftOptions: [CachedPremiumGiftOption] = [] + } + + private let presentationData: PresentationData + + private var peerDisposable: Disposable? + private var titleDisposable: Disposable? + private var preloadSavedMessagesChatsDisposable: Disposable? + + private var preloadHistoryPeerId: PeerId? + private let preloadHistoryPeerIdDisposable = MetaDisposable() + + private var preloadNextChatPeerId: PeerId? + private let preloadNextChatPeerIdDisposable = MetaDisposable() + + private var nextChannelToReadDisposable: Disposable? + private let chatAdditionalDataDisposable = MetaDisposable() + private var premiumOrStarsRequiredDisposable: Disposable? + private var buttonKeyboardMessageDisposable: Disposable? + private var cachedDataDisposable: Disposable? + private var premiumGiftSuggestionDisposable: Disposable? + private var translationStateDisposable: Disposable? + + private let isPeerInfoReady = ValuePromise(false, ignoreRepeated: true) + private let isChatLocationInfoReady = ValuePromise(false, ignoreRepeated: true) + private let isCachedDataReady = ValuePromise(false, ignoreRepeated: true) + + let chatLocationInfoData: ChatLocationInfoData + + private(set) var state: State = State() + var initialInterfaceState: (interfaceState: ChatInterfaceState, editMessage: Message?)? + var initialNavigationBadge: String? + + var overlayTitle: String? { + var title: String? + if let threadInfo = self.state.threadInfo { + title = threadInfo.title + } else if let peerView = self.state.peerView { + if let peer = peerViewMainPeer(peerView) { + title = EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) + } + } + return title + } + + let isReady = Promise() + var onUpdated: ((State) -> Void)? + + let scrolledToMessageId = ValuePromise(nil, ignoreRepeated: true) + var scrolledToMessageIdValue: ScrolledToMessageId? = nil { + didSet { + self.scrolledToMessageId.set(self.scrolledToMessageIdValue) + } + } + + let chatThemeEmoticonPromise = Promise() + let chatWallpaperPromise = Promise() + + var inviteRequestsContext: PeerInvitationImportersContext? + private var inviteRequestsDisposable = MetaDisposable() + + init( + context: AccountContext, + chatLocation: ChatLocation, + chatLocationContextHolder: Atomic, + initialSubject: ChatControllerSubject?, + mode: ChatControllerPresentationMode, + configuration: Signal, + adMessagesContext: AdMessagesHistoryContext?, + currentChatListFilter: Int32?, + customChatNavigationStack: [EnginePeer.Id]?, + presentationData: PresentationData, + historyNode: ChatHistoryListNodeImpl + ) { + self.presentationData = presentationData + let strings = self.presentationData.strings + + let chatLocationPeerId: PeerId? = chatLocation.peerId + let peerId = chatLocationPeerId + + switch chatLocation { + case .peer: + self.chatLocationInfoData = .peer(Promise()) + case let .replyThread(replyThreadMessage): + let promise = Promise() + if let effectiveMessageId = replyThreadMessage.effectiveMessageId { + promise.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: effectiveMessageId)) + |> map { message -> Message? in + guard let message = message else { + return nil + } + return message._asMessage() + }) + } else { + promise.set(.single(nil)) + } + self.chatLocationInfoData = .replyThread(promise) + case .customChatContents: + self.chatLocationInfoData = .customChatContents + } + + if let peerId = chatLocation.peerId, peerId != context.account.peerId { + switch initialSubject { + case .pinnedMessages, .scheduledMessages, .messageOptions: + break + default: + self.state.navigationUserInfo = PeerInfoNavigationSourceTag(peerId: peerId, threadId: chatLocation.threadId) + } + } + + let managingBot: Signal + if let peerId = chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser { + managingBot = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.ChatManagingBot(id: peerId) + ) + |> mapToSignal { result -> Signal in + guard let result else { + return .single(nil) + } + return context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: result.id) + ) + |> map { botPeer -> ChatManagingBot? in + guard let botPeer else { + return nil + } + + return ChatManagingBot(bot: botPeer, isPaused: result.isPaused, canReply: result.canReply, settingsUrl: result.manageUrl) + } + } + |> distinctUntilChanged + } else { + managingBot = .single(nil) + } + + if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId { + peerView.set(context.account.viewTracker.peerView(peerId)) + var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil)) + var hasScheduledMessages: Signal = .single(false) + + if peerId.namespace == Namespaces.Peer.CloudChannel { + let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView.get() + |> map { view -> Bool? in + if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { + if case .broadcast = peer.info { + return nil + } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { + return true + } else { + return false + } + } else { + return false + } + } + |> distinctUntilChanged + |> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in + if let isLarge = isLarge { + if isLarge { + return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) + |> map { value -> (total: Int32?, recent: Int32?) in + return (nil, value) + } + } else { + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map { value -> (total: Int32?, recent: Int32?) in + return (value.total, value.recent) + } + } + } else { + return .single((nil, nil)) + } + } + onlineMemberCount = recentOnlineSignal + } + + var isScheduledOrPinnedMessages = false + switch initialSubject { + case .scheduledMessages, .pinnedMessages, .messageOptions: + isScheduledOrPinnedMessages = true + default: + break + } + + if chatLocation.peerId != nil, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat { + hasScheduledMessages = peerView.get() + |> take(1) + |> mapToSignal { view -> Signal in + 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 + } + } + } + } + + var displayedCountSignal: Signal = .single(nil) + var subtitleTextSignal: Signal = .single(nil) + if case .pinnedMessages = initialSubject { + displayedCountSignal = ChatControllerImpl.topPinnedMessageSignal(context: context, chatLocation: chatLocation, referenceMessage: nil) + |> map { message -> Int? in + return message?.totalCount + } + |> distinctUntilChanged + } else if case let .messageOptions(peerIds, messageIds, info) = initialSubject { + displayedCountSignal = configuration + |> map { configuration -> Int? in + if let selectionState = configuration.selectionState { + return selectionState.selectedIds.count + } else { + return messageIds.count + } + } + |> distinctUntilChanged + + let peers = context.account.postbox.multiplePeersView(peerIds) + |> take(1) + + switch info { + case let .forward(forward): + subtitleTextSignal = combineLatest(peers, forward.options, displayedCountSignal) + |> map { peersView, options, count in + let peers = peersView.peers.values + if !peers.isEmpty { + if peers.count == 1, let peer = peers.first { + if let peer = peer as? TelegramUser { + let displayName = EnginePeer(peer).compactDisplayTitle + if count == 1 { + if options.hideNames { + return strings.Conversation_ForwardOptions_UserMessageForwardHidden(displayName).string + } else { + return strings.Conversation_ForwardOptions_UserMessageForwardVisible(displayName).string + } + } else { + if options.hideNames { + return strings.Conversation_ForwardOptions_UserMessagesForwardHidden(displayName).string + } else { + return strings.Conversation_ForwardOptions_UserMessagesForwardVisible(displayName).string + } + } + } else if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if count == 1 { + if options.hideNames { + return strings.Conversation_ForwardOptions_ChannelMessageForwardHidden + } else { + return strings.Conversation_ForwardOptions_ChannelMessageForwardVisible + } + } else { + if options.hideNames { + return strings.Conversation_ForwardOptions_ChannelMessagesForwardHidden + } else { + return strings.Conversation_ForwardOptions_ChannelMessagesForwardVisible + } + } + } else { + if count == 1 { + if options.hideNames { + return strings.Conversation_ForwardOptions_GroupMessageForwardHidden + } else { + return strings.Conversation_ForwardOptions_GroupMessageForwardVisible + } + } else { + if options.hideNames { + return strings.Conversation_ForwardOptions_GroupMessagesForwardHidden + } else { + return strings.Conversation_ForwardOptions_GroupMessagesForwardVisible + } + } + } + } else { + if count == 1 { + if options.hideNames { + return strings.Conversation_ForwardOptions_RecipientsMessageForwardHidden + } else { + return strings.Conversation_ForwardOptions_RecipientsMessageForwardVisible + } + } else { + if options.hideNames { + return strings.Conversation_ForwardOptions_RecipientsMessagesForwardHidden + } else { + return strings.Conversation_ForwardOptions_RecipientsMessagesForwardVisible + } + } + } + } else { + return nil + } + } + case let .reply(reply): + subtitleTextSignal = reply.selectionState.get() + |> map { selectionState -> String? in + if !selectionState.canQuote { + return nil + } + return strings.Chat_SubtitleQuoteSelectionTip + } + case let .link(link): + subtitleTextSignal = link.options + |> map { options -> String? in + if options.hasAlternativeLinks { + return strings.Chat_SubtitleLinkListTip + } else { + return nil + } + } + |> distinctUntilChanged + } + } + + let hasPeerInfo: Signal + if peerId == context.account.peerId { + hasPeerInfo = .single(true) + |> then( + hasAvailablePeerInfoMediaPanes(context: context, peerId: peerId) + ) + } else { + hasPeerInfo = .single(true) + } + + enum MessageOptionsTitleInfo { + case reply(hasQuote: Bool) + } + let messageOptionsTitleInfo: Signal + if case let .messageOptions(_, _, info) = initialSubject { + switch info { + case .forward, .link: + messageOptionsTitleInfo = .single(nil) + case let .reply(reply): + messageOptionsTitleInfo = reply.selectionState.get() + |> map { selectionState -> Bool in + return selectionState.quote != nil + } + |> distinctUntilChanged + |> map { hasQuote -> MessageOptionsTitleInfo in + return .reply(hasQuote: hasQuote) + } + } + } else { + messageOptionsTitleInfo = .single(nil) + } + + self.titleDisposable = (combineLatest( + queue: Queue.mainQueue(), + peerView.get(), + onlineMemberCount, + displayedCountSignal, + subtitleTextSignal, + configuration, + hasPeerInfo, + messageOptionsTitleInfo + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, configuration, hasPeerInfo, messageOptionsTitleInfo in + guard let strongSelf = self else { + return + } + + let previousState = strongSelf.state + + var isScheduledMessages = false + if case .scheduledMessages = configuration.subject { + isScheduledMessages = true + } + + if case let .messageOptions(_, _, info) = configuration.subject { + if case .reply = info { + let titleContent: ChatTitleContent + if case let .reply(hasQuote) = messageOptionsTitleInfo, hasQuote { + titleContent = .custom(strings.Chat_TitleQuoteSelection, subtitleText, false) + } else { + titleContent = .custom(strings.Chat_TitleReply, subtitleText, false) + } + + strongSelf.state.chatTitleContent = titleContent + } else if case .link = info { + strongSelf.state.chatTitleContent = .custom(strings.Chat_TitleLinkOptions, subtitleText, false) + } else if displayedCount == 1 { + strongSelf.state.chatTitleContent = .custom(strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false) + } else { + strongSelf.state.chatTitleContent = .custom(strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1)), subtitleText, false) + } + } else if let selectionState = configuration.selectionState { + if selectionState.selectedIds.count > 0 { + strongSelf.state.chatTitleContent = .custom(strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count)), nil, false) + } else { + if let reportReason = configuration.reportReason { + strongSelf.state.chatTitleContent = .custom(reportReason.title, strings.Conversation_SelectMessages, false) + } else { + strongSelf.state.chatTitleContent = .custom(strings.Conversation_SelectMessages, nil, false) + } + } + } else if let peer = peerViewMainPeer(peerView) { + if case .pinnedMessages = configuration.subject { + strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) + } else if let channel = peer as? TelegramChannel, channel.isMonoForum { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { + //TODO:localize + strongSelf.state.chatTitleContent = .custom("\(mainPeer.debugDisplayTitle) Messages", nil, false) + } else { + strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, false) + } + } else { + strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) + + let imageOverride: AvatarNodeImageOverride? + if context.account.peerId == peer.id { + imageOverride = .savedMessagesIcon + } else if peer.id.isReplies { + imageOverride = .repliesIcon + } else if peer.id.isAnonymousSavedMessages { + imageOverride = .anonymousSavedMessagesIcon(isColored: true) + } else if peer.isDeleted { + imageOverride = .deletedIcon + } else { + imageOverride = nil + } + + let infoContextActionIsEnabled: Bool + if case .standard(.previewing) = mode { + infoContextActionIsEnabled = false + } else { + infoContextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) == nil + } + + strongSelf.state.infoAvatar = .peer( + peer: EnginePeer(peer), + imageOverride: imageOverride, + contextActionIsEnabled: infoContextActionIsEnabled, + accessibilityLabel: strings.Conversation_ContextMenuOpenProfile + ) + + strongSelf.state.storyStats = peerView.storyStats + } + } + + strongSelf.isPeerInfoReady.set(true) + strongSelf.onUpdated?(previousState) + }) + + let threadInfo: Signal + if let threadId = chatLocation.threadId { + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) + threadInfo = context.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> EngineMessageHistoryThread.Info? in + guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { + return nil + } + guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { + return nil + } + return data.info + } + |> distinctUntilChanged + } else { + threadInfo = .single(nil) + } + + let hasSearchTags: Signal + if let peerId = chatLocation.peerId, peerId == context.account.peerId { + hasSearchTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } else { + hasSearchTags = .single(false) + } + + let hasSavedChats: Signal + if case .peer(context.account.peerId) = chatLocation { + hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved() + } else { + hasSavedChats = .single(false) + } + + let isPremiumRequiredForMessaging: Signal + if let peerId = chatLocation.peerId { + isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId) + |> distinctUntilChanged + } else { + isPremiumRequiredForMessaging = .single(false) + } + + let adMessage: Signal + if let adMessagesContext { + adMessage = adMessagesContext.state |> map { $0.messages.first } + } else { + adMessage = .single(nil) + } + + let displayedPeerVerification: Signal + if let peerId = chatLocation.peerId { + displayedPeerVerification = ApplicationSpecificNotice.displayedPeerVerification(accountManager: context.sharedContext.accountManager, peerId: peerId) + |> take(1) + } else { + displayedPeerVerification = .single(false) + } + + let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy()) + + self.peerDisposable = combineLatest( + queue: Queue.mainQueue(), + peerView.get(), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()), + onlineMemberCount, + hasScheduledMessages, + displayedCountSignal, + threadInfo, + hasSearchTags, + hasSavedChats, + isPremiumRequiredForMessaging, + managingBot, + adMessage, + displayedPeerVerification, + globalPrivacySettings + ).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, adMessage, displayedPeerVerification, globalPrivacySettings in + guard let strongSelf = self else { + return + } + + let previousState = strongSelf.state + + if strongSelf.state.peerView === peerView + && strongSelf.state.hasScheduledMessages == hasScheduledMessages + && strongSelf.state.threadInfo == threadInfo + && strongSelf.state.hasSearchTags == hasSearchTags + && strongSelf.state.hasSavedChats == hasSavedChats + && strongSelf.state.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging + && managingBot == strongSelf.state.contactStatus?.managingBot + && adMessage?.id == strongSelf.state.adMessage?.id { + return + } + + strongSelf.state.hasScheduledMessages = hasScheduledMessages + + var upgradedToPeerId: PeerId? + var movedToForumTopics = false + if let previous = strongSelf.state.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { + upgradedToPeerId = migrationReference.peerId + } + if let previous = strongSelf.state.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, !channel.isForumOrMonoForum, let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.isForumOrMonoForum { + movedToForumTopics = true + } + + var shouldDismiss = false + if let previous = strongSelf.state.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.membership != .Removed, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, updatedGroup.membership == .Removed { + shouldDismiss = true + } else if let previous = strongSelf.state.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, channel.participationStatus != .kicked, let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.participationStatus == .kicked { + shouldDismiss = true + } else if let previous = strongSelf.state.peerView, let secretChat = previous.peers[previous.peerId] as? TelegramSecretChat, case .active = secretChat.embeddedState, let updatedSecretChat = peerView.peers[peerView.peerId] as? TelegramSecretChat, case .terminated = updatedSecretChat.embeddedState { + shouldDismiss = true + } + + var wasGroupChannel: Bool? + if let previousPeerView = strongSelf.state.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + wasGroupChannel = true + } else { + wasGroupChannel = false + } + } + var isGroupChannel: Bool? + if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + isGroupChannel = true + } else { + isGroupChannel = false + } + } + let firstTime = strongSelf.state.peerView == nil + strongSelf.state.peerView = peerView + strongSelf.state.threadInfo = threadInfo + if wasGroupChannel != isGroupChannel { + if let isGroupChannel = isGroupChannel, isGroupChannel { + let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let disposable = DisposableSet() + disposable.add(recentDisposable) + disposable.add(adminsDisposable) + strongSelf.chatAdditionalDataDisposable.set(disposable) + } else { + strongSelf.chatAdditionalDataDisposable.set(nil) + } + } + + var peerIsMuted = false + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } else if case .default = notificationSettings.muteState { + if let peer = peerView.peers[peerView.peerId] { + if peer is TelegramUser { + peerIsMuted = !globalNotificationSettings.privateChats.enabled + } else if peer is TelegramGroup { + peerIsMuted = !globalNotificationSettings.groupChats.enabled + } else if let channel = peer as? TelegramChannel { + switch channel.info { + case .group: + peerIsMuted = !globalNotificationSettings.groupChats.enabled + case .broadcast: + peerIsMuted = !globalNotificationSettings.channels.enabled + } + } + } + } + } + var starGiftsAvailable = false + var peerDiscussionId: PeerId? + var peerGeoLocation: PeerGeoLocation? + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { + if case .broadcast = peer.info { + starGiftsAvailable = cachedData.flags.contains(.starGiftsAvailable) + } else { + peerGeoLocation = cachedData.peerGeoLocation + } + if case let .known(value) = cachedData.linkedDiscussionPeerId { + peerDiscussionId = value + } + } + var renderedPeer: RenderedPeer? + var contactStatus: ChatContactStatus? + var businessIntro: TelegramBusinessIntro? + var sendPaidMessageStars: StarsAmount? + var alwaysShowGiftButton = false + var disallowedGifts: TelegramDisallowedGifts? + if let peer = peerView.peers[peerView.peerId] { + if let cachedData = peerView.cachedData as? CachedUserData { + contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot) + if case let .known(value) = cachedData.businessIntro { + businessIntro = value + } + if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { + } else { + sendPaidMessageStars = cachedData.sendPaidMessageStars + if cachedData.disallowedGifts != .All { + alwaysShowGiftButton = globalPrivacySettings.displayGiftButton || cachedData.flags.contains(.displayGiftButton) + } + disallowedGifts = cachedData.disallowedGifts + } + } else if let cachedData = peerView.cachedData as? CachedGroupData { + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) + } else if let cachedData = peerView.cachedData as? CachedChannelData { + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) + + if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { + if channel.flags.contains(.isCreator) || channel.adminRights != nil { + } else { + sendPaidMessageStars = channel.sendPaidMessageStars + } + } + } + + var peers = SimpleDictionary() + peers[peer.id] = peer + if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { + peers[associatedPeer.id] = associatedPeer + } + renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media) + } + + var isNotAccessible: Bool = false + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + isNotAccessible = cachedChannelData.isNotAccessible + } + + if firstTime && isNotAccessible { + context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) + } + + var hasBots: Bool = false + var hasBotCommands: Bool = false + var botMenuButton: BotMenuButton = .commands + var currentSendAsPeerId: PeerId? + var autoremoveTimeout: Int32? + var copyProtectionEnabled: Bool = false + var hasBirthdayToday = false + var peerVerification: PeerVerification? + if let peer = peerView.peers[peerView.peerId] { + if !displayedPeerVerification { + if let cachedUserData = peerView.cachedData as? CachedUserData { + peerVerification = cachedUserData.verification + } else if let cachedChannelData = peerView.cachedData as? CachedChannelData { + peerVerification = cachedChannelData.verification + } + } + copyProtectionEnabled = peer.isCopyProtectionEnabled + if let cachedGroupData = peerView.cachedData as? CachedGroupData { + if !cachedGroupData.botInfos.isEmpty { + hasBots = true + } + let botCommands = cachedGroupData.botInfos.reduce(into: [], { result, info in + result.append(contentsOf: info.botInfo.commands) + }) + if !botCommands.isEmpty { + hasBotCommands = true + } + if case let .known(value) = cachedGroupData.autoremoveTimeout { + autoremoveTimeout = value?.effectiveValue + } + } else if let cachedChannelData = peerView.cachedData as? CachedChannelData { + if let channel = peer as? TelegramChannel, channel.isMonoForum { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + currentSendAsPeerId = channel.linkedMonoforumId + } else { + currentSendAsPeerId = nil + } + } else { + currentSendAsPeerId = cachedChannelData.sendAsPeerId + if let channel = peer as? TelegramChannel, case .group = channel.info { + if !cachedChannelData.botInfos.isEmpty { + hasBots = true + } + let botCommands = cachedChannelData.botInfos.reduce(into: [], { result, info in + result.append(contentsOf: info.botInfo.commands) + }) + if !botCommands.isEmpty { + hasBotCommands = true + } + } + } + if case let .known(value) = cachedChannelData.autoremoveTimeout { + autoremoveTimeout = value?.effectiveValue + } + } else if let cachedUserData = peerView.cachedData as? CachedUserData { + botMenuButton = cachedUserData.botInfo?.menuButton ?? .commands + if case let .known(value) = cachedUserData.autoremoveTimeout { + autoremoveTimeout = value?.effectiveValue + } + if let botInfo = cachedUserData.botInfo, !botInfo.commands.isEmpty { + hasBotCommands = true + } + if let birthday = cachedUserData.birthday { + let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date()) + if today.day == Int(birthday.day) && today.month == Int(birthday.month) { + hasBirthdayToday = true + } + } + } + } + + let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive + + var explicitelyCanPinMessages: Bool = false + if let cachedUserData = peerView.cachedData as? CachedUserData { + explicitelyCanPinMessages = cachedUserData.canPinMessages + } else if peerView.peerId == context.account.peerId { + explicitelyCanPinMessages = true + } + + if strongSelf.preloadHistoryPeerId != peerDiscussionId { + strongSelf.preloadHistoryPeerId = peerDiscussionId + if let peerDiscussionId = peerDiscussionId, let channel = peerView.peers[peerView.peerId] as? TelegramChannel, case .broadcast = channel.info { + let combinedDisposable = DisposableSet() + strongSelf.preloadHistoryPeerIdDisposable.set(combinedDisposable) + combinedDisposable.add(context.account.viewTracker.polledChannel(peerId: peerDiscussionId).startStrict()) + combinedDisposable.add(context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) + } else { + strongSelf.preloadHistoryPeerIdDisposable.set(nil) + } + } + + var appliedBoosts: Int32? + var boostsToUnrestrict: Int32? + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + appliedBoosts = cachedChannelData.appliedBoosts + boostsToUnrestrict = cachedChannelData.boostsToUnrestrict + } + + if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId { + strongSelf.premiumOrStarsRequiredDisposable = ((context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone() + } + + var adMessage = adMessage + if let peer = peerView.peers[peerView.peerId] as? TelegramUser, peer.botInfo != nil { + } else { + adMessage = nil + } + + strongSelf.state.isNotAccessible = isNotAccessible + strongSelf.state.contactStatus = contactStatus + strongSelf.state.hasBots = hasBots + strongSelf.state.hasBotCommands = hasBotCommands + strongSelf.state.botMenuButton = botMenuButton + strongSelf.state.isArchived = isArchived + strongSelf.state.peerIsMuted = peerIsMuted + strongSelf.state.peerDiscussionId = peerDiscussionId + strongSelf.state.peerGeoLocation = peerGeoLocation + strongSelf.state.explicitelyCanPinMessages = explicitelyCanPinMessages + strongSelf.state.hasScheduledMessages = hasScheduledMessages + strongSelf.state.autoremoveTimeout = autoremoveTimeout + strongSelf.state.currentSendAsPeerId = currentSendAsPeerId + strongSelf.state.copyProtectionEnabled = copyProtectionEnabled + strongSelf.state.hasSearchTags = hasSearchTags + strongSelf.state.isPremiumRequiredForMessaging = isPremiumRequiredForMessaging + strongSelf.state.sendPaidMessageStars = sendPaidMessageStars + strongSelf.state.alwaysShowGiftButton = alwaysShowGiftButton + strongSelf.state.disallowedGifts = disallowedGifts + strongSelf.state.hasSavedChats = hasSavedChats + strongSelf.state.appliedBoosts = appliedBoosts + strongSelf.state.boostsToUnrestrict = boostsToUnrestrict + strongSelf.state.hasBirthdayToday = hasBirthdayToday + strongSelf.state.businessIntro = businessIntro + strongSelf.state.adMessage = adMessage + strongSelf.state.peerVerification = peerVerification + strongSelf.state.starGiftsAvailable = starGiftsAvailable + + strongSelf.state.renderedPeer = renderedPeer + strongSelf.state.adMessage = adMessage + + if case .standard(.default) = mode, let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info { + var isRegularChat = false + if let subject = initialSubject { + if case .message = subject { + isRegularChat = true + } + } else { + isRegularChat = true + } + if strongSelf.nextChannelToReadDisposable == nil, let peerId = chatLocation.peerId, let customChatNavigationStack { + if let index = customChatNavigationStack.firstIndex(of: peerId), index != customChatNavigationStack.count - 1 { + let nextPeerId = customChatNavigationStack[index + 1] + strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(), + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: nextPeerId) + ), + ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: context.sharedContext.accountManager) + ) + |> then(.complete() |> delay(1.0, queue: .mainQueue())) + |> restart).startStrict(next: { [weak strongSelf] nextPeer, nextChatSuggestionTip in + guard let strongSelf else { + return + } + + let previousState = strongSelf.state + + strongSelf.state.offerNextChannelToRead = true + strongSelf.state.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in + return (peer: nextPeer, threadData: nil, unreadCount: 0, location: .same) + } + strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 + + let nextPeerId = nextPeer?.id + + if strongSelf.preloadNextChatPeerId != nextPeerId { + strongSelf.preloadNextChatPeerId = nextPeerId + if let nextPeerId = nextPeerId { + let combinedDisposable = DisposableSet() + strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable) + combinedDisposable.add(context.account.viewTracker.polledChannel(peerId: nextPeerId).startStrict()) + combinedDisposable.add(context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId)) + } else { + strongSelf.preloadNextChatPeerIdDisposable.set(nil) + } + } + + strongSelf.onUpdated?(previousState) + }) + } + } else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil { + //TODO:loc optimize + let accountPeerId = context.account.peerId + strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(), + context.engine.peers.getNextUnreadChannel(peerId: channel.id, chatListFilterId: currentChatListFilter, getFilterPredicate: { data in + return chatListFilterPredicate(filter: data, accountPeerId: accountPeerId) + }), + ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: context.sharedContext.accountManager) + ) + |> then(.complete() |> delay(1.0, queue: .mainQueue())) + |> restart).startStrict(next: { [weak strongSelf] nextPeer, nextChatSuggestionTip in + guard let strongSelf else { + return + } + + let previousState = strongSelf.state + + strongSelf.state.offerNextChannelToRead = true + strongSelf.state.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in + return (peer: nextPeer.peer, threadData: nil, unreadCount: nextPeer.unreadCount, location: nextPeer.location) + } + strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 + + let nextPeerId = nextPeer?.peer.id + + if strongSelf.preloadNextChatPeerId != nextPeerId { + strongSelf.preloadNextChatPeerId = nextPeerId + if let nextPeerId = nextPeerId { + let combinedDisposable = DisposableSet() + strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable) + combinedDisposable.add(context.account.viewTracker.polledChannel(peerId: nextPeerId).startStrict()) + combinedDisposable.add(context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId)) + } else { + strongSelf.preloadNextChatPeerIdDisposable.set(nil) + } + } + + strongSelf.onUpdated?(previousState) + }) + } + } + + if let upgradedToPeerId { + strongSelf.state.performDismissAction = .upgraded(upgradedToPeerId) + } else if movedToForumTopics { + strongSelf.state.performDismissAction = .movedToForumTopics + } else if shouldDismiss { + strongSelf.state.performDismissAction = .dismiss + } + + strongSelf.isChatLocationInfoReady.set(true) + strongSelf.onUpdated?(previousState) + }) + + if peerId == context.account.peerId { + self.preloadSavedMessagesChatsDisposable?.dispose() + self.preloadSavedMessagesChatsDisposable = context.engine.messages.savedMessagesPeerListHead().start() + } + } else if case let .replyThread(messagePromise) = self.chatLocationInfoData, let peerId = peerId { + self.isPeerInfoReady.set(true) + + let replyThreadType: ChatTitleContent.ReplyThreadType + var replyThreadId: Int64? + switch chatLocation { + case .peer: + replyThreadType = .replies + case let .replyThread(replyThreadMessage): + if replyThreadMessage.peerId == context.account.peerId { + replyThreadId = replyThreadMessage.threadId + replyThreadType = .replies + } else { + replyThreadId = replyThreadMessage.threadId + if replyThreadMessage.isChannelPost { + replyThreadType = .comments + } else { + replyThreadType = .replies + } + } + case .customChatContents: + replyThreadType = .replies + } + + let peerView = context.account.viewTracker.peerView(peerId) + + let messageAndTopic = messagePromise.get() + |> mapToSignal { message -> Signal<(message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int), NoError> in + guard let replyThreadId = replyThreadId else { + return .single((message, nil, 0)) + } + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: replyThreadId) + let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Cloud, customTag: nil) + let localCountViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Local, customTag: nil) + return context.account.postbox.combinedView(keys: [viewKey, countViewKey, localCountViewKey]) + |> map { views -> (message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int) in + guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { + return (message, nil, 0) + } + var messageCount = 0 + if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { + if replyThreadId == 1 { + messageCount += Int(count) + } else { + messageCount += max(Int(count) - 1, 0) + } + } + if let summaryView = views.views[localCountViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { + messageCount += Int(count) + } + return (message, view.info?.data.get(MessageHistoryThreadData.self), messageCount) + } + } + + let savedMessagesPeerId: PeerId? + if case let .replyThread(replyThreadMessage) = chatLocation, (replyThreadMessage.peerId == context.account.peerId || replyThreadMessage.isMonoforumPost) { + savedMessagesPeerId = PeerId(replyThreadMessage.threadId) + } else { + savedMessagesPeerId = nil + } + + let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)?, NoError> + if let savedMessagesPeerId { + let threadPeerId = savedMessagesPeerId + let basicPeerKey: PostboxViewKey = .peer(peerId: threadPeerId, components: []) + let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: savedMessagesPeerId.toInt64(), namespace: Namespaces.Message.Cloud, customTag: nil) + savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey]) + |> map { views -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)? in + var peer: EnginePeer? + var presence: EnginePeer.Presence? + if let peerView = views.views[basicPeerKey] as? PeerView { + peer = peerViewMainPeer(peerView).flatMap(EnginePeer.init) + presence = peerView.peerPresences[threadPeerId].flatMap(EnginePeer.Presence.init) + } + + var messageCount = 0 + if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { + messageCount += Int(count) + } + + return (peer, messageCount, presence) + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs?.peer != rhs?.peer { + return false + } + if lhs?.messageCount != rhs?.messageCount { + return false + } + if lhs?.presence != rhs?.presence { + return false + } + return true + }) + } else { + savedMessagesPeer = .single(nil) + } + + var isScheduledOrPinnedMessages = false + switch initialSubject { + case .scheduledMessages, .pinnedMessages, .messageOptions: + isScheduledOrPinnedMessages = true + default: + break + } + + var hasScheduledMessages: Signal = .single(false) + if chatLocation.peerId != nil, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat { + let chatLocationContextHolder = chatLocationContextHolder + hasScheduledMessages = peerView + |> take(1) + |> mapToSignal { view -> Signal in + if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) { + return .single(false) + } else { + 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 + } + } + } + } + + var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil)) + if peerId.namespace == Namespaces.Peer.CloudChannel { + let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView + |> map { view -> Bool? in + if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { + if case .broadcast = peer.info { + return nil + } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { + return true + } else { + return false + } + } else { + return false + } + } + |> distinctUntilChanged + |> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in + if let isLarge = isLarge { + if isLarge { + return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) + |> map { value -> (total: Int32?, recent: Int32?) in + return (nil, value) + } + } else { + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map { value -> (total: Int32?, recent: Int32?) in + return (value.total, value.recent) + } + } + } else { + return .single((nil, nil)) + } + } + onlineMemberCount = recentOnlineSignal + } + + let hasSearchTags: Signal + if let peerId = chatLocation.peerId, peerId == context.account.peerId { + hasSearchTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } else { + hasSearchTags = .single(false) + } + + let hasSavedChats: Signal + if case .peer(context.account.peerId) = chatLocation { + hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved() + } else { + hasSavedChats = .single(false) + } + + let isPremiumRequiredForMessaging: Signal + if let peerId = chatLocation.peerId { + isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId) + |> distinctUntilChanged + } else { + isPremiumRequiredForMessaging = .single(false) + } + + let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy()) + + self.peerDisposable = (combineLatest(queue: Queue.mainQueue(), + peerView, + messageAndTopic, + savedMessagesPeer, + onlineMemberCount, + hasScheduledMessages, + hasSearchTags, + hasSavedChats, + isPremiumRequiredForMessaging, + managingBot, + globalPrivacySettings + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, globalPrivacySettings in + guard let strongSelf = self else { + return + } + + let previousState = strongSelf.state + + strongSelf.state.hasScheduledMessages = hasScheduledMessages + + var renderedPeer: RenderedPeer? + var contactStatus: ChatContactStatus? + var copyProtectionEnabled = false + var businessIntro: TelegramBusinessIntro? + var sendPaidMessageStars: StarsAmount? + var alwaysShowGiftButton = false + var disallowedGifts: TelegramDisallowedGifts? + if let peer = peerView.peers[peerView.peerId] { + copyProtectionEnabled = peer.isCopyProtectionEnabled + if let cachedData = peerView.cachedData as? CachedUserData { + contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot) + if case let .known(value) = cachedData.businessIntro { + businessIntro = value + } + if cachedData.disallowedGifts != .All { + alwaysShowGiftButton = globalPrivacySettings.displayGiftButton || cachedData.flags.contains(.displayGiftButton) + } + disallowedGifts = cachedData.disallowedGifts + } else if let cachedData = peerView.cachedData as? CachedGroupData { + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) + } else if let cachedData = peerView.cachedData as? CachedChannelData { + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) + + if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { + if channel.flags.contains(.isCreator) || channel.adminRights != nil { + } else { + sendPaidMessageStars = channel.sendPaidMessageStars + } + } + } + + var peers = SimpleDictionary() + peers[peer.id] = peer + if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { + peers[associatedPeer.id] = associatedPeer + } + renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media) + } + + if let savedMessagesPeerId { + var peerPresences: [PeerId: PeerPresence] = [:] + if let presence = savedMessagesPeer?.presence { + peerPresences[savedMessagesPeerId] = presence._asPresence() + } + let mappedPeerData = ChatTitleContent.PeerData( + peerId: savedMessagesPeerId, + peer: savedMessagesPeer?.peer?._asPeer(), + isContact: true, + isSavedMessages: true, + notificationSettings: nil, + peerPresences: peerPresences, + cachedData: nil + ) + + var customMessageCount: Int? + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.isMonoForum { + } else { + customMessageCount = savedMessagesPeer?.messageCount ?? 0 + } + + strongSelf.state.chatTitleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true) + + strongSelf.state.peerView = peerView + + let imageOverride: AvatarNodeImageOverride? + if context.account.peerId == savedMessagesPeerId { + imageOverride = .myNotesIcon + } else if let peer = savedMessagesPeer?.peer, peer.id.isReplies { + imageOverride = .repliesIcon + } else if let peer = savedMessagesPeer?.peer, peer.id.isAnonymousSavedMessages { + imageOverride = .anonymousSavedMessagesIcon(isColored: true) + } else if let peer = savedMessagesPeer?.peer, peer.isDeleted { + imageOverride = .deletedIcon + } else { + imageOverride = nil + } + + if let peer = savedMessagesPeer?.peer { + var infoContextActionIsEnabled = false + if case .standard(.previewing) = mode { + infoContextActionIsEnabled = false + } else { + infoContextActionIsEnabled = true + } + strongSelf.state.infoAvatar = .peer( + peer: peer, + imageOverride: imageOverride, + contextActionIsEnabled: infoContextActionIsEnabled, + accessibilityLabel: strings.Conversation_ContextMenuOpenProfile + ) + } + + strongSelf.state.renderedPeer = renderedPeer + strongSelf.state.savedMessagesTopicPeer = savedMessagesPeer?.peer + strongSelf.state.hasSearchTags = hasSearchTags + strongSelf.state.hasSavedChats = hasSavedChats + strongSelf.state.hasScheduledMessages = hasScheduledMessages + } else { + let message = messageAndTopic.message + + var count = 0 + if let message = message { + for attribute in message.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute { + count = Int(attribute.count) + break + } + } + } + + var peerIsMuted = false + if let threadData = messageAndTopic.threadData { + if case let .muted(until) = threadData.notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } + } else if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } + } + + if let threadInfo = messageAndTopic.threadData?.info { + strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true) + + let avatarContent: EmojiStatusComponent.Content + if chatLocation.threadId == 1 { + avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme), tintColor: nil) + } else if let fileId = threadInfo.icon { + avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1)) + } else { + avatarContent = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: 32.0, height: 32.0)) + } + + var infoContextActionIsEnabled = false + if case .standard(.previewing) = mode { + infoContextActionIsEnabled = false + } else { + infoContextActionIsEnabled = true + } + strongSelf.state.infoAvatar = .emojiStatus(content: avatarContent, contextActionIsEnabled: infoContextActionIsEnabled) + } else { + strongSelf.state.chatTitleContent = .replyThread(type: replyThreadType, count: count) + } + + var wasGroupChannel: Bool? + if let previousPeerView = strongSelf.state.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + wasGroupChannel = true + } else { + wasGroupChannel = false + } + } + var isGroupChannel: Bool? + if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + isGroupChannel = true + } else { + isGroupChannel = false + } + } + let firstTime = strongSelf.state.peerView == nil + + if wasGroupChannel != isGroupChannel { + if let isGroupChannel = isGroupChannel, isGroupChannel { + let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let disposable = DisposableSet() + disposable.add(recentDisposable) + disposable.add(adminsDisposable) + strongSelf.chatAdditionalDataDisposable.set(disposable) + } else { + strongSelf.chatAdditionalDataDisposable.set(nil) + } + } + + strongSelf.state.peerView = peerView + strongSelf.state.threadInfo = messageAndTopic.threadData?.info + + var peerDiscussionId: PeerId? + var peerGeoLocation: PeerGeoLocation? + var currentSendAsPeerId: PeerId? + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { + if peer.isMonoForum { + if let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + currentSendAsPeerId = peer.linkedMonoforumId + } else { + currentSendAsPeerId = nil + } + } else { + currentSendAsPeerId = cachedData.sendAsPeerId + if case .group = peer.info { + peerGeoLocation = cachedData.peerGeoLocation + } + if case let .known(value) = cachedData.linkedDiscussionPeerId { + peerDiscussionId = value + } + } + } + + var isNotAccessible: Bool = false + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + isNotAccessible = cachedChannelData.isNotAccessible + } + + if firstTime && isNotAccessible { + context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) + } + + var hasBots: Bool = false + if let peer = peerView.peers[peerView.peerId] { + if let cachedGroupData = peerView.cachedData as? CachedGroupData { + if !cachedGroupData.botInfos.isEmpty { + hasBots = true + } + } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { + if !cachedChannelData.botInfos.isEmpty { + hasBots = true + } + } + } + + let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive + + var explicitelyCanPinMessages: Bool = false + if let cachedUserData = peerView.cachedData as? CachedUserData { + explicitelyCanPinMessages = cachedUserData.canPinMessages + } else if peerView.peerId == context.account.peerId { + explicitelyCanPinMessages = true + } + + if strongSelf.preloadHistoryPeerId != peerDiscussionId { + strongSelf.preloadHistoryPeerId = peerDiscussionId + if let peerDiscussionId = peerDiscussionId { + strongSelf.preloadHistoryPeerIdDisposable.set(context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) + } else { + strongSelf.preloadHistoryPeerIdDisposable.set(nil) + } + } + + var appliedBoosts: Int32? + var boostsToUnrestrict: Int32? + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + appliedBoosts = cachedChannelData.appliedBoosts + boostsToUnrestrict = cachedChannelData.boostsToUnrestrict + } + + if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId { + strongSelf.premiumOrStarsRequiredDisposable = ((context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone() + } + + strongSelf.state.renderedPeer = renderedPeer + strongSelf.state.isNotAccessible = isNotAccessible + strongSelf.state.contactStatus = contactStatus + strongSelf.state.hasBots = hasBots + strongSelf.state.isArchived = isArchived + strongSelf.state.peerIsMuted = peerIsMuted + strongSelf.state.peerDiscussionId = peerDiscussionId + strongSelf.state.peerGeoLocation = peerGeoLocation + strongSelf.state.explicitelyCanPinMessages = explicitelyCanPinMessages + strongSelf.state.hasScheduledMessages = hasScheduledMessages + strongSelf.state.currentSendAsPeerId = currentSendAsPeerId + strongSelf.state.copyProtectionEnabled = copyProtectionEnabled + strongSelf.state.hasSearchTags = hasSearchTags + strongSelf.state.isPremiumRequiredForMessaging = isPremiumRequiredForMessaging + strongSelf.state.hasSavedChats = hasSavedChats + strongSelf.state.appliedBoosts = appliedBoosts + strongSelf.state.boostsToUnrestrict = boostsToUnrestrict + strongSelf.state.businessIntro = businessIntro + strongSelf.state.sendPaidMessageStars = sendPaidMessageStars + strongSelf.state.alwaysShowGiftButton = alwaysShowGiftButton + strongSelf.state.disallowedGifts = disallowedGifts + + if let replyThreadId, let channel = renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, strongSelf.nextChannelToReadDisposable == nil { + strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(), + context.engine.peers.getNextUnreadForumTopic(peerId: channel.id, topicId: Int32(clamping: replyThreadId)), + ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: context.sharedContext.accountManager) + ) + |> then(.complete() |> delay(1.0, queue: .mainQueue())) + |> restart).startStrict(next: { nextThreadData, nextChatSuggestionTip in + guard let strongSelf = self else { + return + } + + let previousState = strongSelf.state + + strongSelf.state.offerNextChannelToRead = true + strongSelf.state.nextChannelToRead = nextThreadData.flatMap { nextThreadData -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in + return (peer: EnginePeer(channel), threadData: nextThreadData, unreadCount: Int(nextThreadData.data.incomingUnreadCount), location: .same) + } + strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 + + strongSelf.onUpdated?(previousState) + }) + } + } + + strongSelf.isChatLocationInfoReady.set(true) + strongSelf.onUpdated?(previousState) + }) + } else if case .customChatContents = self.chatLocationInfoData { + self.titleDisposable?.dispose() + self.titleDisposable = nil + self.isPeerInfoReady.set(true) + + let peerView: Signal = .single(nil) + + if case let .customChatContents(customChatContents) = initialSubject { + switch customChatContents.kind { + case .hashTagSearch: + break + case let .quickReplyMessageInput(shortcut, shortcutType): + switch shortcutType { + case .generic: + self.state.chatTitleContent = .custom("\(shortcut)", nil, false) + case .greeting: + self.state.chatTitleContent = .custom(strings.QuickReply_TitleGreetingMessage, nil, false) + case .away: + self.state.chatTitleContent = .custom(strings.QuickReply_TitleAwayMessage, nil, false) + } + case let .businessLinkSetup(link): + let linkUrl: String + if link.url.hasPrefix("https://") { + linkUrl = String(link.url[link.url.index(link.url.startIndex, offsetBy: "https://".count)...]) + } else { + linkUrl = link.url + } + + self.state.chatTitleContent = .custom(link.title ?? strings.Business_Links_EditLinkTitle, linkUrl, false) + } + } else { + self.state.chatTitleContent = .custom(" ", nil, false) + } + + self.peerDisposable = (peerView + |> deliverOnMainQueue).startStrict(next: { [weak self] peerView in + guard let self else { + return + } + + let previousState = self.state + + var renderedPeer: RenderedPeer? + if let peerView, let peer = peerView.peers[peerView.peerId] { + var peers = SimpleDictionary() + peers[peer.id] = peer + if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { + peers[associatedPeer.id] = associatedPeer + } + renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media) + + self.state.infoAvatar = .peer( + peer: EnginePeer(peer), + imageOverride: nil, + contextActionIsEnabled: false, + accessibilityLabel: nil + ) + } else { + self.state.infoAvatar = nil + } + + self.state.peerView = peerView + self.state.renderedPeer = renderedPeer + + self.isChatLocationInfoReady.set(true) + self.onUpdated?(previousState) + }) + } + + let initialData = historyNode.initialData + |> take(1) + |> deliverOnMainQueue + |> beforeNext { [weak self] combinedInitialData in + guard let strongSelf = self, let combinedInitialData else { + return + } + + let previousState = strongSelf.state + + if let opaqueState = (combinedInitialData.initialData?.storedInterfaceState).flatMap(_internal_decodeStoredChatInterfaceState) { + var interfaceState = ChatInterfaceState.parse(opaqueState) + + var pinnedMessageId: MessageId? + var peerIsBlocked: Bool = false + var callsAvailable: Bool = true + var callsPrivate: Bool = false + var activeGroupCallInfo: ChatActiveGroupCallInfo? + var slowmodeState: ChatSlowmodeState? + if let cachedData = combinedInitialData.cachedData as? CachedChannelData { + pinnedMessageId = cachedData.pinnedMessageId + + var canBypassRestrictions = false + if let boostsToUnrestrict = cachedData.boostsToUnrestrict, let appliedBoosts = cachedData.appliedBoosts, appliedBoosts >= boostsToUnrestrict { + canBypassRestrictions = true + } + if !canBypassRestrictions, let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { + if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { + slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) + } + } + if let activeCall = cachedData.activeCall { + activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) + } + } else if let cachedData = combinedInitialData.cachedData as? CachedUserData { + peerIsBlocked = cachedData.isBlocked + callsAvailable = cachedData.voiceCallsAvailable + callsPrivate = cachedData.callsPrivate + pinnedMessageId = cachedData.pinnedMessageId + } else if let cachedData = combinedInitialData.cachedData as? CachedGroupData { + pinnedMessageId = cachedData.pinnedMessageId + if let activeCall = cachedData.activeCall { + activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) + } + } else if let _ = combinedInitialData.cachedData as? CachedSecretChatData { + } + + if let channel = combinedInitialData.initialData?.peer as? TelegramChannel { + if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) + } else if channel.hasBannedPermission(.banSendVoice) != nil { + if channel.hasBannedPermission(.banSendInstantVideos) == nil { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) + } + } else if channel.hasBannedPermission(.banSendInstantVideos) != nil { + if channel.hasBannedPermission(.banSendVoice) == nil { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) + } + } + } else if let group = combinedInitialData.initialData?.peer as? TelegramGroup { + if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) + } else if group.hasBannedPermission(.banSendVoice) { + if !group.hasBannedPermission(.banSendInstantVideos) { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) + } + } else if group.hasBannedPermission(.banSendInstantVideos) { + if !group.hasBannedPermission(.banSendVoice) { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) + } + } + } + + if case let .replyThread(replyThreadMessageId) = chatLocation { + if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isForumOrMonoForum { + pinnedMessageId = nil + } else { + pinnedMessageId = replyThreadMessageId.effectiveTopId + } + } + + var pinnedMessage: ChatPinnedMessage? + if let pinnedMessageId = pinnedMessageId { + if let cachedDataMessages = combinedInitialData.cachedDataMessages { + if let message = cachedDataMessages[pinnedMessageId] { + pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id) + } + } + } + + var buttonKeyboardMessage = combinedInitialData.buttonKeyboardMessage + if let buttonKeyboardMessageValue = buttonKeyboardMessage, buttonKeyboardMessageValue.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with({ $0 })) { + buttonKeyboardMessage = nil + } + + strongSelf.state.pinnedMessageId = pinnedMessageId + strongSelf.state.pinnedMessage = pinnedMessage + strongSelf.state.keyboardButtonsMessage = buttonKeyboardMessage + strongSelf.state.peerIsBlocked = peerIsBlocked + strongSelf.state.callsAvailable = callsAvailable + strongSelf.state.callsPrivate = callsPrivate + strongSelf.state.activeGroupCallInfo = activeGroupCallInfo + strongSelf.state.slowmodeState = slowmodeState + + var initialEditMessage: Message? + if let editMessage = interfaceState.editMessage, let message = combinedInitialData.initialData?.associatedMessages[editMessage.messageId] { + initialEditMessage = message + } + + strongSelf.initialInterfaceState = (interfaceState, initialEditMessage) + } + if let readStateData = combinedInitialData.readStateData { + if case let .peer(peerId) = chatLocation, let peerReadStateData = readStateData[peerId], let notificationSettings = peerReadStateData.notificationSettings { + + let inAppSettings = context.sharedContext.currentInAppNotificationSettings.with { $0 } + let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: peerReadStateData.totalState ?? ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:])) + + var globalRemainingUnreadChatCount = count + if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && peerReadStateData.unreadCount > 0 { + if case .messages = inAppSettings.totalUnreadCountDisplayCategory { + globalRemainingUnreadChatCount -= peerReadStateData.unreadCount + } else { + globalRemainingUnreadChatCount -= 1 + } + } + if globalRemainingUnreadChatCount > 0 { + strongSelf.initialNavigationBadge = "\(globalRemainingUnreadChatCount)" + } else { + strongSelf.initialNavigationBadge = "" + } + } + } + + strongSelf.onUpdated?(previousState) + } + + self.isReady.set(combineLatest(queue: .mainQueue(), [ + self.isPeerInfoReady.get(), + self.isChatLocationInfoReady.get(), + self.isCachedDataReady.get(), + historyNode.isReady, + initialData |> map { _ -> Bool in true } + ]) + |> map { values in + return !values.contains(where: { !$0 }) + } + |> filter { $0 } + |> take(1) + |> distinctUntilChanged) + + self.buttonKeyboardMessageDisposable?.dispose() + self.buttonKeyboardMessageDisposable = historyNode.buttonKeyboardMessage.startStrict(next: { [weak self] message in + guard let strongSelf = self else { + return + } + var buttonKeyboardMessageUpdated = false + if let currentButtonKeyboardMessage = strongSelf.state.keyboardButtonsMessage, let message = message { + if currentButtonKeyboardMessage.id != message.id || currentButtonKeyboardMessage.stableVersion != message.stableVersion { + buttonKeyboardMessageUpdated = true + } + } else if (strongSelf.state.keyboardButtonsMessage != nil) != (message != nil) { + buttonKeyboardMessageUpdated = true + } + if buttonKeyboardMessageUpdated { + let previousState = strongSelf.state + strongSelf.state.keyboardButtonsMessage = message + strongSelf.onUpdated?(previousState) + } + }) + + if let peerId = chatLocation.peerId { + let customEmojiAvailable: Signal = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.SecretChatLayer(id: peerId) + ) + |> map { layer -> Bool in + guard let layer = layer else { + return true + } + + return layer >= 144 + } + |> distinctUntilChanged + + let isForum = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> map { peer -> Bool in + if case let .channel(channel) = peer { + return channel.isForumOrMonoForum + } else { + return false + } + } + |> distinctUntilChanged + + let threadData: Signal + let forumTopicData: Signal + if let threadId = chatLocation.threadId { + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) + threadData = context.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> ChatPresentationInterfaceState.ThreadData? in + guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { + return nil + } + guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { + return nil + } + return ChatPresentationInterfaceState.ThreadData(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor, isOwnedByMe: data.isOwnedByMe, isClosed: data.isClosed) + } + |> distinctUntilChanged + forumTopicData = .single(nil) + } else { + forumTopicData = isForum + |> mapToSignal { isForum -> Signal in + if isForum { + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: 1) + return context.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> ChatPresentationInterfaceState.ThreadData? in + guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { + return nil + } + guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { + return nil + } + return ChatPresentationInterfaceState.ThreadData(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor, isOwnedByMe: data.isOwnedByMe, isClosed: data.isClosed) + } + |> distinctUntilChanged + } else { + return .single(nil) + } + } + threadData = .single(nil) + } + + if case .standard(.previewing) = mode { + } else if peerId.namespace != Namespaces.Peer.SecretChat && peerId != context.account.peerId && initialSubject != .scheduledMessages { + self.premiumGiftSuggestionDisposable?.dispose() + self.premiumGiftSuggestionDisposable = (ApplicationSpecificNotice.dismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peerId) + |> deliverOnMainQueue).startStrict(next: { [weak self] timestamp in + guard let strongSelf = self else { + return + } + + let previousState = strongSelf.state + + let currentTime = Int32(Date().timeIntervalSince1970) + var suggest = true + if let timestamp, currentTime < timestamp + 60 * 60 * 24 { + suggest = false + } + strongSelf.state.suggestPremiumGift = suggest + + strongSelf.onUpdated?(previousState) + }) + + var baseLanguageCode = self.presentationData.strings.baseLanguageCode + if baseLanguageCode.contains("-") { + baseLanguageCode = baseLanguageCode.components(separatedBy: "-").first ?? baseLanguageCode + } + let isPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> Bool in + return peer?.isPremium ?? false + } |> distinctUntilChanged + + let isHidden = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.TranslationHidden(id: peerId)) + |> distinctUntilChanged + + let hasAutoTranslate = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AutoTranslateEnabled(id: peerId)) + |> distinctUntilChanged + + self.translationStateDisposable?.dispose() + self.translationStateDisposable = (combineLatest( + queue: .concurrentDefaultQueue(), + isPremium, + isHidden, + hasAutoTranslate, + ApplicationSpecificNotice.translationSuggestion(accountManager: context.sharedContext.accountManager) + ) |> mapToSignal { isPremium, isHidden, hasAutoTranslate, counterAndTimestamp -> Signal in + var maybeSuggestPremium = false + if counterAndTimestamp.0 >= 3 { + maybeSuggestPremium = true + } + if (isPremium || maybeSuggestPremium || hasAutoTranslate) && !isHidden { + return chatTranslationState(context: context, peerId: peerId, threadId: chatLocation.threadId) + |> map { translationState -> ChatPresentationTranslationState? in + if let translationState, !translationState.fromLang.isEmpty && (translationState.fromLang != baseLanguageCode || translationState.isEnabled) { + return ChatPresentationTranslationState(isEnabled: translationState.isEnabled, fromLang: translationState.fromLang, toLang: translationState.toLang ?? baseLanguageCode) + } else { + return nil + } + } + |> distinctUntilChanged + } else { + return .single(nil) + } + } + |> deliverOnMainQueue).startStrict(next: { [weak self] chatTranslationState in + guard let strongSelf = self else { + return + } + + let previousState = strongSelf.state + + strongSelf.state.translationState = chatTranslationState + + strongSelf.onUpdated?(previousState) + }) + } + + let premiumGiftOptions: Signal<[CachedPremiumGiftOption], NoError> = .single([]) + |> then( + context.engine.payments.premiumGiftCodeOptions(peerId: peerId, onlyCached: true) + |> map { options in + return options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } + } + ) + + let isTopReplyThreadMessageShown: Signal = historyNode.isTopReplyThreadMessageShown.get() + |> distinctUntilChanged + + let hasPendingMessages: Signal + let chatLocationPeerId = chatLocation.peerId + + if let chatLocationPeerId = chatLocationPeerId { + hasPendingMessages = context.account.pendingMessageManager.hasPendingMessages + |> mapToSignal { peerIds -> Signal in + let value = peerIds.contains(chatLocationPeerId) + if value { + return .single(true) + } else { + return .single(false) + } + } + |> distinctUntilChanged + } else { + hasPendingMessages = .single(false) + } + + let topPinnedMessage: Signal + if let subject = initialSubject { + switch subject { + case .messageOptions, .pinnedMessages, .scheduledMessages: + topPinnedMessage = .single(nil) + default: + topPinnedMessage = ChatControllerImpl.topPinnedScrollMessage(context: context, chatLocation: chatLocation, historyNode: historyNode, scrolledToMessageId: self.scrolledToMessageId.get()) + } + } else { + topPinnedMessage = ChatControllerImpl.topPinnedScrollMessage(context: context, chatLocation: chatLocation, historyNode: historyNode, scrolledToMessageId: self.scrolledToMessageId.get()) + } + + self.cachedDataDisposable?.dispose() + self.cachedDataDisposable = combineLatest(queue: .mainQueue(), historyNode.cachedPeerDataAndMessages, + hasPendingMessages, + isTopReplyThreadMessageShown, + topPinnedMessage, + customEmojiAvailable, + isForum, + threadData, + forumTopicData, + premiumGiftOptions + ).startStrict(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, customEmojiAvailable, isForum, threadData, forumTopicData, premiumGiftOptions in + guard let strongSelf = self else { + return + } + + let previousState = strongSelf.state + + let (cachedData, messages) = cachedDataAndMessages + + if cachedData != nil { + var themeEmoticon: String? = nil + var chatWallpaper: TelegramWallpaper? + if let cachedData = cachedData as? CachedUserData { + themeEmoticon = cachedData.themeEmoticon + chatWallpaper = cachedData.wallpaper + } else if let cachedData = cachedData as? CachedGroupData { + themeEmoticon = cachedData.themeEmoticon + } else if let cachedData = cachedData as? CachedChannelData { + themeEmoticon = cachedData.themeEmoticon + chatWallpaper = cachedData.wallpaper + } + + strongSelf.chatThemeEmoticonPromise.set(.single(themeEmoticon)) + strongSelf.chatWallpaperPromise.set(.single(chatWallpaper)) + } + + var pinnedMessageId: MessageId? + var peerIsBlocked: Bool = false + var callsAvailable: Bool = false + var callsPrivate: Bool = false + var voiceMessagesAvailable: Bool = true + var slowmodeState: ChatSlowmodeState? + var activeGroupCallInfo: ChatActiveGroupCallInfo? + var inviteRequestsPending: Int32? + if let cachedData = cachedData as? CachedChannelData { + pinnedMessageId = cachedData.pinnedMessageId + if !canBypassRestrictions(boostsToUnrestrict: strongSelf.state.boostsToUnrestrict, appliedBoosts: strongSelf.state.appliedBoosts) { + if let channel = strongSelf.state.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { + if hasPendingMessages { + slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .pendingMessages) + } else if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { + slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) + } + } + } + if let activeCall = cachedData.activeCall { + activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) + } + inviteRequestsPending = cachedData.inviteRequestsPending + } else if let cachedData = cachedData as? CachedUserData { + peerIsBlocked = cachedData.isBlocked + callsAvailable = cachedData.voiceCallsAvailable + callsPrivate = cachedData.callsPrivate + pinnedMessageId = cachedData.pinnedMessageId + voiceMessagesAvailable = cachedData.voiceMessagesAvailable + } else if let cachedData = cachedData as? CachedGroupData { + pinnedMessageId = cachedData.pinnedMessageId + if let activeCall = cachedData.activeCall { + activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) + } + inviteRequestsPending = cachedData.inviteRequestsPending + } else if let _ = cachedData as? CachedSecretChatData { + } + + var pinnedMessage: ChatPinnedMessage? + switch chatLocation { + case let .replyThread(replyThreadMessage): + if isForum { + pinnedMessageId = topPinnedMessage?.message.id + pinnedMessage = topPinnedMessage + } else { + if isTopReplyThreadMessageShown { + pinnedMessageId = nil + } else { + pinnedMessageId = replyThreadMessage.effectiveTopId + } + if let pinnedMessageId = pinnedMessageId { + if let message = messages?[pinnedMessageId] { + pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id) + } + } + } + case .peer: + pinnedMessageId = topPinnedMessage?.message.id + pinnedMessage = topPinnedMessage + case .customChatContents: + pinnedMessageId = nil + pinnedMessage = nil + } + + var pinnedMessageUpdated = false + if let current = strongSelf.state.pinnedMessage, let updated = pinnedMessage { + if current != updated { + pinnedMessageUpdated = true + } + } else if (strongSelf.state.pinnedMessage != nil) != (pinnedMessage != nil) { + pinnedMessageUpdated = true + } + + let callsDataUpdated = strongSelf.state.callsAvailable != callsAvailable || strongSelf.state.callsPrivate != callsPrivate + + let voiceMessagesAvailableUpdated = strongSelf.state.voiceMessagesAvailable != voiceMessagesAvailable + + var canManageInvitations = false + if let channel = strongSelf.state.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) { + canManageInvitations = true + } else if let group = strongSelf.state.renderedPeer?.peer as? TelegramGroup { + if case .creator = group.role { + canManageInvitations = true + } else if case let .admin(rights, _) = group.role, rights.rights.contains(.canInviteUsers) { + canManageInvitations = true + } + } + + if canManageInvitations, let inviteRequestsPending = inviteRequestsPending, inviteRequestsPending >= 0 { + if strongSelf.inviteRequestsContext == nil { + let inviteRequestsContext = context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil)) + strongSelf.inviteRequestsContext = inviteRequestsContext + + strongSelf.inviteRequestsDisposable.set((combineLatest(queue: Queue.mainQueue(), inviteRequestsContext.state, ApplicationSpecificNotice.dismissedInvitationRequests(accountManager: context.sharedContext.accountManager, peerId: peerId))).startStrict(next: { [weak strongSelf] requestsState, dismissedInvitationRequests in + guard let strongSelf else { + return + } + + let previousState = strongSelf.state + + strongSelf.state.requestsState = requestsState + strongSelf.state.dismissedInvitationRequests = dismissedInvitationRequests + + strongSelf.onUpdated?(previousState) + })) + } else if let inviteRequestsContext = strongSelf.inviteRequestsContext { + let _ = (inviteRequestsContext.state + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak inviteRequestsContext] state in + if state.count != inviteRequestsPending { + inviteRequestsContext?.loadMore() + } + }) + } + } + + var isUpdated = false + if strongSelf.state.pinnedMessageId != pinnedMessageId || strongSelf.state.pinnedMessage != pinnedMessage || strongSelf.state.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || voiceMessagesAvailableUpdated || strongSelf.state.slowmodeState != slowmodeState || strongSelf.state.activeGroupCallInfo != activeGroupCallInfo || customEmojiAvailable != strongSelf.state.customEmojiAvailable || threadData != strongSelf.state.threadData || forumTopicData != strongSelf.state.forumTopicData || premiumGiftOptions != strongSelf.state.premiumGiftOptions { + isUpdated = true + + strongSelf.state.pinnedMessage = pinnedMessage + strongSelf.state.pinnedMessageId = pinnedMessageId + strongSelf.state.activeGroupCallInfo = activeGroupCallInfo + strongSelf.state.peerIsBlocked = peerIsBlocked + strongSelf.state.callsAvailable = callsAvailable + strongSelf.state.callsPrivate = callsPrivate + strongSelf.state.voiceMessagesAvailable = voiceMessagesAvailable + strongSelf.state.customEmojiAvailable = customEmojiAvailable + strongSelf.state.threadData = threadData + strongSelf.state.forumTopicData = forumTopicData + strongSelf.state.isGeneralThreadClosed = forumTopicData?.isClosed + strongSelf.state.premiumGiftOptions = premiumGiftOptions + strongSelf.state.slowmodeState = slowmodeState + } + + strongSelf.isCachedDataReady.set(true) + + if isUpdated { + strongSelf.onUpdated?(previousState) + } + }) + } else { + self.isCachedDataReady.set(true) + } + } + + deinit { + self.peerDisposable?.dispose() + self.titleDisposable?.dispose() + self.preloadSavedMessagesChatsDisposable?.dispose() + self.preloadHistoryPeerIdDisposable.dispose() + self.preloadNextChatPeerIdDisposable.dispose() + self.nextChannelToReadDisposable?.dispose() + self.chatAdditionalDataDisposable.dispose() + self.premiumOrStarsRequiredDisposable?.dispose() + self.buttonKeyboardMessageDisposable?.dispose() + self.cachedDataDisposable?.dispose() + self.premiumGiftSuggestionDisposable?.dispose() + self.translationStateDisposable?.dispose() + self.inviteRequestsDisposable.dispose() + } + } +} diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 43fd6d9cb3..06f9eaef63 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -314,6 +314,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { private var loadMoreSearchResultsDisposable: Disposable? + let adMessagesContext: AdMessagesHistoryContext? + private var isLoadingValue: Bool = false private var isLoadingEarlier: Bool = false private func updateIsLoading(isLoading: Bool, earlier: Bool, animated: Bool) { @@ -667,9 +669,26 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } self.controllerInteraction.chatIsRotated = historyNodeRotated + + var displayAdPeer: PeerId? + if !isChatPreview { + switch subject { + case .none, .message: + if case let .peer(peerId) = chatLocation { + displayAdPeer = peerId + } + default: + break + } + } + if let displayAdPeer { + self.adMessagesContext = context.engine.messages.adMessages(peerId: displayAdPeer) + } else { + self.adMessagesContext = nil + } var getMessageTransitionNode: (() -> ChatMessageTransitionNodeImpl?)? - self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: controller?.updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: nil, source: source, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), rotated: historyNodeRotated, isChatPreview: isChatPreview, messageTransitionNode: { + self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: controller?.updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: self.adMessagesContext, tag: nil, source: source, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), rotated: historyNodeRotated, isChatPreview: isChatPreview, messageTransitionNode: { return getMessageTransitionNode?() }) @@ -1371,11 +1390,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - #if DEBUG + /*#if DEBUG if "".isEmpty { hasTranslationPanel = true } - #endif + #endif*/ if hasTranslationPanel { let translationPanelNode: ChatTranslationPanelNode @@ -4330,7 +4349,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let effectiveInputText = expandedInputStateAttributedString(effectivePresentationInterfaceState.interfaceState.composeInputState.inputText) - let peerSpecificEmojiPack = (self.controller?.peerView?.cachedData as? CachedChannelData)?.emojiPack + let peerSpecificEmojiPack = (self.controller?.contentData?.state.peerView?.cachedData as? CachedChannelData)?.emojiPack var inlineStickers: [MediaId: Media] = [:] var firstLockedPremiumEmoji: TelegramMediaFile? @@ -5041,6 +5060,27 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { return leftIndex < rightIndex } + func createHistoryNodeForChatLocation(chatLocation: ChatLocation, chatLocationContextHolder: Atomic) -> ChatHistoryListNodeImpl { + let historyNode = ChatHistoryListNodeImpl( + context: self.context, + updatedPresentationData: self.controller?.updatedPresentationData ?? (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), + chatLocation: chatLocation, + chatLocationContextHolder: chatLocationContextHolder, + adMessagesContext: self.adMessagesContext, + tag: nil, + source: .default, + subject: nil, + controllerInteraction: self.controllerInteraction, + selectedMessages: self.selectedMessagesPromise.get(), + rotated: self.controllerInteraction.chatIsRotated, + isChatPreview: false, + messageTransitionNode: { + return nil + } + ) + return historyNode + } + func updateChatLocation(chatLocation: ChatLocation, transition: ContainedViewLayoutTransition, tabSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection?) { if chatLocation == self.chatLocation { return @@ -5053,6 +5093,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { updatedPresentationData: self.controller?.updatedPresentationData ?? (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, + adMessagesContext: self.adMessagesContext, tag: nil, source: .default, subject: nil, diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index e4ed508f02..dc6721eaef 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -850,7 +850,7 @@ extension ChatControllerImpl { let controller = legacyAttachmentMenu( context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, - threadTitle: strongSelf.threadInfo?.title, chatLocation: strongSelf.chatLocation, + threadTitle: strongSelf.contentData?.state.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, addingMedia: editMediaOptions == nil, saveEditedPhotos: settings.storeEditedPhotos, @@ -1203,14 +1203,14 @@ extension ChatControllerImpl { isScheduledMessages = true } var paidMediaAllowed = false - if let cachedData = self.peerView?.cachedData as? CachedChannelData, cachedData.flags.contains(.paidMediaAllowed) { + if let cachedData = self.contentData?.state.peerView?.cachedData as? CachedChannelData, cachedData.flags.contains(.paidMediaAllowed) { paidMediaAllowed = true } let controller = MediaPickerScreenImpl( context: self.context, updatedPresentationData: self.updatedPresentationData, peer: (self.presentationInterfaceState.renderedPeer?.peer).flatMap(EnginePeer.init), - threadTitle: self.threadInfo?.title, + threadTitle: self.contentData?.state.threadInfo?.title, chatLocation: self.chatLocation, isScheduledMessages: isScheduledMessages, bannedSendPhotos: bannedSendPhotos, @@ -1369,7 +1369,7 @@ extension ChatControllerImpl { slowModeEnabled = true } - let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, threadTitle: strongSelf.threadInfo?.title, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, selectionLimit: selectionLimit).startStandalone(next: { generator in + let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, threadTitle: strongSelf.contentData?.state.threadInfo?.title, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, selectionLimit: selectionLimit).startStandalone(next: { generator in if let strongSelf = self { let legacyController = LegacyController(presentation: fileMode ? .navigation : .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) legacyController.navigationPresentation = .modal diff --git a/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift b/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift index 5d7e3c0f88..60fc23d926 100644 --- a/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift +++ b/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift @@ -20,6 +20,6 @@ public extension ChatControllerImpl { if let foundItemNode, let message = foundItemNode.item?.message { self.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(Set([message.stableId])) } - self.chatDisplayNode.historyNode.adMessagesContext?.remove(opaqueId: opaqueId) + self.chatDisplayNode.adMessagesContext?.remove(opaqueId: opaqueId) } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 5a03a0dfba..e7928e43d4 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -475,7 +475,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto private(set) var tag: HistoryViewInputTag? private let controllerInteraction: ChatControllerInteraction private let selectedMessages: Signal?, NoError> - private let messageTransitionNode: () -> ChatMessageTransitionNodeImpl? + var messageTransitionNode: () -> ChatMessageTransitionNodeImpl? private let mode: ChatHistoryListMode private var enableUnreadAlignment: Bool = true @@ -707,7 +707,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto var openNextChannelToRead: ((EnginePeer, (id: Int64, data: MessageHistoryThreadData)?, TelegramEngine.NextUnreadChannelLocation) -> Void)? private var contentInsetAnimator: DisplayLinkAnimator? - let adMessagesContext: AdMessagesHistoryContext? + private let adMessagesContext: AdMessagesHistoryContext? private var adMessagesDisposable: Disposable? private var preloadAdPeerName: String? private let preloadAdPeerDisposable = MetaDisposable() @@ -753,7 +753,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto private let initTimestamp: Double - 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, isChatPreview: Bool, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, adMessagesContext: AdMessagesHistoryContext?, tag: HistoryViewInputTag?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, rotated: Bool = false, isChatPreview: Bool, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) { self.initTimestamp = CFAbsoluteTimeGetCurrent() var tag = tag @@ -788,21 +788,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.prefetchManager = InChatPrefetchManager(context: context) - var displayAdPeer: PeerId? - if !isChatPreview { - switch subject { - case .none, .message: - if case let .peer(peerId) = chatLocation { - displayAdPeer = peerId - } - default: - break - } - } + self.adMessagesContext = adMessagesContext var adMessages: Signal<(interPostInterval: Int32?, messages: [Message]), NoError> - if case .bubbles = mode, let peerId = displayAdPeer { - let adMessagesContext = context.engine.messages.adMessages(peerId: peerId) - self.adMessagesContext = adMessagesContext + if case .bubbles = mode, let adMessagesContext { + let peerId = adMessagesContext.peerId if peerId.namespace == Namespaces.Peer.CloudUser { adMessages = .single((nil, [])) } else { @@ -891,7 +880,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } } } else { - self.adMessagesContext = nil adMessages = .single((nil, [])) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 4b1907421b..f4ada44683 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -153,8 +153,6 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat displayActionsPanel = true } else if peerStatusSettings.contains(.canShareContact) { displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true } else if peerStatusSettings.contains(.suggestAddMembers) { displayActionsPanel = true } @@ -232,6 +230,10 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat } func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? { + //TODO:release + if "".isEmpty { + return nil + } if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil { let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId { diff --git a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift index d0e6c904cd..9f172725b0 100644 --- a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift @@ -23,7 +23,6 @@ private enum ChatReportPeerTitleButton: Equatable { case shareMyPhoneNumber case reportSpam case reportUserSpam - case reportIrrelevantGeoLocation case unarchive case addMembers case restartTopic @@ -48,8 +47,6 @@ private enum ChatReportPeerTitleButton: Equatable { return strings.Conversation_ReportSpamAndLeave case .reportUserSpam: return strings.Conversation_ReportSpam - case .reportIrrelevantGeoLocation: - return strings.Conversation_ReportGroupLocation case .unarchive: return strings.Conversation_Unarchive case .addMembers: @@ -125,8 +122,6 @@ private func peerButtons(_ state: ChatPresentationInterfaceState) -> [ChatReport if case .peer = state.chatLocation { if let contactStatus = state.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.suggestAddMembers) { buttons.append(.addMembers) - } else if let contactStatus = state.contactStatus, contactStatus.canReportIrrelevantLocation, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - buttons.append(.reportIrrelevantGeoLocation) } else if let contactStatus = state.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings, peerStatusSettings.contains(.autoArchived) { buttons.append(.reportUserSpam) buttons.append(.unarchive) @@ -792,8 +787,6 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { self.interfaceInteraction?.presentInviteMembers() case .addContact: self.interfaceInteraction?.presentPeerContact() - case .reportIrrelevantGeoLocation: - self.interfaceInteraction?.reportPeerIrrelevantGeoLocation() case .restartTopic: self.interfaceInteraction?.restartTopic() } diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index e21416010d..c1f3276d74 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -233,7 +233,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu self.isGlobalSearch = false } - self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) + self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) self.historyNode.clipsToBounds = true super.init() @@ -576,7 +576,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } let chatLocationContextHolder = Atomic(value: nil) - let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: .default, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) + let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: .default, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) historyNode.clipsToBounds = true historyNode.preloadPages = true historyNode.stackFromBottom = true diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index b830f4c93b..3ce34c88eb 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2228,6 +2228,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { updatedPresentationData: updatedPresentationData, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, + adMessagesContext: nil, tag: tag, source: source, subject: subject, diff --git a/submodules/ffmpeg/BUILD b/submodules/ffmpeg/BUILD index f17be250cb..e60ed3ec8d 100644 --- a/submodules/ffmpeg/BUILD +++ b/submodules/ffmpeg/BUILD @@ -286,11 +286,15 @@ genrule( PATH="$$PATH:$$YASM_DIR" "$$SOURCE_PATH/build-ffmpeg-bazel.sh" "$$VARIANT" "$$BUILD_ARCH" "$$BUILD_DIR" "$$SOURCE_PATH" "$$FFMPEG_VERSION" """ + "\n" + "\n".join([ "cp \"$$BUILD_DIR/FFmpeg-iOS/include/{header_path}\" \"$(location Public/third_party/ffmpeg/{header_path})\"".format(header_path = header_path) for header_path in ffmpeg_header_paths + ]) + "\n" + "\n".join([ + "cp \"$$BUILD_DIR/FFmpeg-iOS/include/{header_path}\" \"$(location Public/{header_path})\"".format(header_path = header_path) for header_path in ffmpeg_header_paths ]) + "\n" + "\n".join([ "cp \"$$BUILD_DIR/FFmpeg-iOS/lib/{lib}\" \"$(location {lib})\"".format(lib = lib) for lib in ffmpeg_libs ]), outs = [ "Public/third_party/ffmpeg/{}".format(header_path) for header_path in ffmpeg_header_paths + ] + [ + "Public/{}".format(header_path) for header_path in ffmpeg_header_paths ] + ffmpeg_libs, tools = [ "//third-party/yasm:yasm.tar", @@ -312,10 +316,9 @@ objc_library( name = "ffmpeg", module_name = "ffmpeg", enable_modules = True, - hdrs = ["Public/third_party/ffmpeg/" + x for x in ffmpeg_header_paths], + hdrs = ["Public/third_party/ffmpeg/" + x for x in ffmpeg_header_paths] + ["Public/" + x for x in ffmpeg_header_paths], includes = [ "Public", - "Public/third_party/ffmpeg", ], sdk_dylibs = [ "libbz2", diff --git a/submodules/openssl/BUILD b/submodules/openssl/BUILD index 2770f12648..88e867860f 100644 --- a/submodules/openssl/BUILD +++ b/submodules/openssl/BUILD @@ -162,6 +162,9 @@ genrule( cc_library( name = "openssl_lib", srcs = [":" + x for x in openssl_libs], + cxxopts = [ + "-std=c++17", + ], ) objc_library( diff --git a/submodules/sqlcipher/BUILD b/submodules/sqlcipher/BUILD index 85e0a5f8c0..266e1d9bf5 100644 --- a/submodules/sqlcipher/BUILD +++ b/submodules/sqlcipher/BUILD @@ -29,6 +29,7 @@ objc_library( "-DNDEBUG=1", "-DSQLITE_MAX_MMAP_SIZE=0", "-Wno-all", + "-Wno-#warnings", ], sdk_frameworks = [ "Foundation", diff --git a/third-party/td/BUILD b/third-party/td/BUILD index 85a5e2d97d..396dde13bb 100644 --- a/third-party/td/BUILD +++ b/third-party/td/BUILD @@ -135,6 +135,10 @@ objc_library( copts = [ "-Werror", ], + cxxopts = [ + "-Werror", + "-std=c++17", + ], includes = [ "TdBinding/Public", ], diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD index 91498518d5..d76f237543 100644 --- a/third-party/webrtc/BUILD +++ b/third-party/webrtc/BUILD @@ -3473,6 +3473,9 @@ cc_library( "-DWEBRTC_HAVE_DCSCTP", "-DWEBRTC_HAVE_SCTP", ] + arch_specific_cflags + optimization_flags, + cxxopts = [ + "-std=c++17", + ], deps = [ "//third-party/boringssl:crypto", "//third-party/boringssl:ssl", @@ -3602,6 +3605,9 @@ objc_library( "-DRTC_DISABLE_TRACE_EVENTS", "-DRTC_DISABLE_METRICS", ] + arch_specific_cflags + optimization_flags, + cxxopts = [ + "-std=c++17", + ], deps = [ "//third-party/boringssl:crypto", "//third-party/boringssl:ssl",