From d01a7853fa71af60e562e3d1e5748a3b83a99fe9 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 28 Sep 2022 12:15:06 +0200 Subject: [PATCH] [WIP] Topic APIs --- Telegram/BUILD | 56 ++ .../Sources/NotificationService.swift | 1 + Tests/TelegramCoreBuildTest/BUILD | 155 +++++ .../TelegramCoreBuildTest/Sources/main.swift | 15 + .../Sources/AccountContext.swift | 2 + .../Sources/ChatListController.swift | 5 + .../Sources/ChatListController.swift | 295 ++++++---- .../Sources/ChatListControllerNode.swift | 43 +- .../Sources/ChatListSearchListPaneNode.swift | 26 +- .../Sources/Node/ChatListItem.swift | 371 +++++++----- .../Sources/Node/ChatListNode.swift | 393 +++++++------ .../Sources/Node/ChatListNodeEntries.swift | 82 ++- .../Sources/Node/ChatListNodeLocation.swift | 101 +++- .../Sources/Node/ChatListTypingNode.swift | 2 +- .../Sources/Node/ChatListViewTransition.swift | 104 ++-- .../Sources/HashtagSearchController.swift | 4 +- .../OpenSSLEncryptionProvider.h | 2 + submodules/Postbox/Sources/Message.swift | 10 + .../MessageHistoryThreadIndexView.swift | 131 +++++ .../Sources/MessageHistoryThreadsTable.swift | 36 ++ .../Sources/MessageThreadIndexTable.swift | 180 ++++++ submodules/Postbox/Sources/Postbox.swift | 20 +- .../Postbox/Sources/PostboxTransaction.swift | 7 +- submodules/Postbox/Sources/Views.swift | 11 + .../TextSizeSelectionController.swift | 9 +- .../ThemeAccentColorControllerNode.swift | 9 +- .../Themes/ThemePreviewControllerNode.swift | 9 +- submodules/TelegramApi/Sources/Api0.swift | 9 + submodules/TelegramApi/Sources/Api10.swift | 550 +++++------------- submodules/TelegramApi/Sources/Api11.swift | 466 +++++++++++++++ submodules/TelegramApi/Sources/Api25.swift | 84 +++ submodules/TelegramApi/Sources/Api29.swift | 111 ++++ submodules/TelegramApi/Sources/Api5.swift | 66 +-- submodules/TelegramApi/Sources/Api6.swift | 150 +++-- submodules/TelegramApi/Sources/Api7.swift | 208 +++---- submodules/TelegramApi/Sources/Api8.swift | 294 ++++------ submodules/TelegramApi/Sources/Api9.swift | 320 +++++----- .../Sources/ApiUtils/ApiGroupOrChannel.swift | 3 + .../ApiUtils/StoreMessage_Telegram.swift | 10 +- .../ApiUtils/TelegramMediaAction.swift | 170 +++--- .../TelegramCore/Sources/ForumChannels.swift | 319 ++++++++++ .../Sources/State/Serialization.swift | 2 +- .../SyncCore/SyncCore_TelegramChannel.swift | 1 + .../SyncCore_TelegramMediaAction.swift | 439 +++++++------- .../TelegramEngine/Data/MessagesData.swift | 2 +- .../TelegramEngine/Messages/ChatList.swift | 159 ++++- .../TelegramEngine/Messages/Message.swift | 48 +- .../Messages/TelegramEngineMessages.swift | 5 +- .../Peers/ChannelCreation.swift | 11 + .../Peers/TelegramEnginePeers.swift | 4 + .../Sources/ServiceMessageStrings.swift | 10 + submodules/TelegramUI/BUILD | 1 + .../Components/ForumTopicListScreen/BUILD | 33 ++ .../Sources/ForumTopicListScreen.swift | 495 ++++++++++++++++ .../ChatSearchResultsContollerNode.swift | 11 +- .../ContactMultiselectionControllerNode.swift | 4 +- .../Sources/CreateGroupController.swift | 21 +- .../Sources/NavigateToChatController.swift | 26 + .../Sources/PeerSelectionControllerNode.swift | 4 +- .../Sources/SharedAccountContext.swift | 10 +- 60 files changed, 4264 insertions(+), 1861 deletions(-) create mode 100644 Tests/TelegramCoreBuildTest/BUILD create mode 100644 Tests/TelegramCoreBuildTest/Sources/main.swift create mode 100644 submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift create mode 100644 submodules/Postbox/Sources/MessageThreadIndexTable.swift create mode 100644 submodules/TelegramCore/Sources/ForumChannels.swift create mode 100644 submodules/TelegramUI/Components/ForumTopicListScreen/BUILD create mode 100644 submodules/TelegramUI/Components/ForumTopicListScreen/Sources/ForumTopicListScreen.swift diff --git a/Telegram/BUILD b/Telegram/BUILD index 67835423e6..95ba60873a 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -980,6 +980,9 @@ ios_framework( deps = [ "//submodules/TelegramCore:TelegramCore", ], + visibility = [ + "//visibility:public", + ] ) plist_fragment( @@ -1466,6 +1469,9 @@ ios_extension( ":PostboxFramework", ":TelegramCoreFramework", ], + visibility = [ + "//visibility:public", + ] ) plist_fragment( @@ -2059,3 +2065,53 @@ ios_application( "//third-party/libvpx:vpx", ], ) + +swift_library( + name = "TelegramCoreBuildTestLib", + module_name = "TelegramCoreBuildTestLib", + srcs = glob([ + "Tests/TelegramCoreBuildTest/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + data = [ + ":WidgetAssets", + ], + deps = [ + "//submodules/BuildConfig:BuildConfig", + "//submodules/WidgetItems:WidgetItems_iOS14", + "//submodules/WidgetItemsUtils:WidgetItemsUtils", + "//submodules/AppLockState:AppLockState", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider", + "//Telegram:GeneratedSources", + ], +) + +ios_application( + name = "TelegramCoreBuildTest", + bundle_id = "{telegram_bundle_id}".format( + telegram_bundle_id = telegram_bundle_id, + ), + families = ["iphone", "ipad"], + minimum_os_version = minimum_os_version, + provisioning_profile = select({ + ":disableProvisioningProfilesSetting": None, + "//conditions:default": "@build_configuration//provisioning:Telegram.mobileprovision", + }), + entitlements = ":TelegramEntitlements.entitlements", + infoplists = [ + ":TelegramInfoPlist", + ":BuildNumberInfoPlist", + ":VersionInfoPlist", + ":RequiredDeviceCapabilitiesPlist", + ":UrlTypesInfoPlist", + ], + frameworks = [ + ":TelegramCoreFramework", + ], + deps = [":TelegramCoreBuildTestLib"], +) diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 61dca23a25..3fa826778b 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -613,6 +613,7 @@ private final class NotificationServiceHandler { private let pollDisposable = MetaDisposable() init?(queue: Queue, updateCurrentContent: @escaping (NotificationContent) -> Void, completed: @escaping () -> Void, payload: [AnyHashable: Any]) { + //debug_linker_fail_test() self.queue = queue let episode = String(UInt32.random(in: 0 ..< UInt32.max), radix: 16) diff --git a/Tests/TelegramCoreBuildTest/BUILD b/Tests/TelegramCoreBuildTest/BUILD new file mode 100644 index 0000000000..8b4227a100 --- /dev/null +++ b/Tests/TelegramCoreBuildTest/BUILD @@ -0,0 +1,155 @@ +load("@build_bazel_rules_apple//apple:ios.bzl", + "ios_application", +) + +load("@build_bazel_rules_swift//swift:swift.bzl", + "swift_library", +) + +load("//build-system/bazel-utils:plist_fragment.bzl", + "plist_fragment", +) + +load( + "@build_configuration//:variables.bzl", + "telegram_bundle_id", + "telegram_aps_environment", + "telegram_team_id", + "telegram_enable_icloud", + "telegram_enable_siri", + "telegram_enable_watch", +) + +module_name = "TelegramCoreBuildTest" + +swift_library( + name = "Lib", + srcs = glob([ + "Sources/**/*.swift", + ]), + data = [ + ], + deps = [ + ], +) + +plist_fragment( + name = "BuildNumberInfoPlist", + extension = "plist", + template = + """ + CFBundleVersion + 1 + """ +) + +plist_fragment( + name = "VersionInfoPlist", + extension = "plist", + template = + """ + CFBundleShortVersionString + {telegramVersion} + """ +) + +plist_fragment( + name = "AppNameInfoPlist", + extension = "plist", + template = + """ + CFBundleDisplayName + Test + """ +) + +plist_fragment( + name = "AppInfoPlist", + extension = "plist", + template = + """ + CFBundleAllowMixedLocalizations + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Test + CFBundleIdentifier + ph.telegra.Telegraph + CFBundleName + Telegram + CFBundlePackageType + APPL + CFBundleSignature + ???? + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIDeviceFamily + + 1 + 2 + + UIFileSharingEnabled + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + UIViewEdgeAntialiasing + + UIViewGroupOpacity + + CADisableMinimumFrameDurationOnPhone + + """.format(module_name=module_name) +) + +ios_application( + name = module_name, + bundle_id = "ph.telegra.Telegraph", + families = ["iphone", "ipad"], + minimum_os_version = "11.0", + provisioning_profile = None, + infoplists = [ + ":AppInfoPlist", + ":BuildNumberInfoPlist", + ":VersionInfoPlist", + ], + resources = [ + "//Tests/Common:LaunchScreen", + ], + extensions = [ + "//Telegram:WidgetExtension", + ], + deps = [ + "//Tests/Common:Main", + ":Lib", + ], + visibility = ["//visibility:public"], +) diff --git a/Tests/TelegramCoreBuildTest/Sources/main.swift b/Tests/TelegramCoreBuildTest/Sources/main.swift new file mode 100644 index 0000000000..9e20a7e8ff --- /dev/null +++ b/Tests/TelegramCoreBuildTest/Sources/main.swift @@ -0,0 +1,15 @@ +import Foundation +import UIKit + +@objc(Application) +public final class Application: UIApplication { +} + +@objc(AppDelegate) +public final class AppDelegate: NSObject, UIApplicationDelegate { + public var window: UIWindow? + + public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + return true + } +} diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 0e686c05f2..72142165bc 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -732,6 +732,8 @@ public protocol SharedAccountContext: AnyObject { func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController func makePrivacyAndSecurityController(context: AccountContext) -> ViewController func navigateToChatController(_ params: NavigateToChatControllerParams) + func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) + func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController) -> Signal func openStorageUsage(context: AccountContext) func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController) func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) diff --git a/submodules/AccountContext/Sources/ChatListController.swift b/submodules/AccountContext/Sources/ChatListController.swift index 4ee38cb3c5..280fd0ad81 100644 --- a/submodules/AccountContext/Sources/ChatListController.swift +++ b/submodules/AccountContext/Sources/ChatListController.swift @@ -4,6 +4,11 @@ import Postbox import Display import TelegramCore +public enum ChatListControllerLocation { + case chatList(groupId: EngineChatList.Group) + case forum(peerId: PeerId) +} + public protocol ChatListController: ViewController { var context: AccountContext { get } var lockViewFrame: CGRect? { get } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index a8b602ebbe..0e6f2056aa 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -118,8 +118,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer - public let groupId: PeerGroupId - public let filter: ChatListFilter? + public let location: ChatListControllerLocation public let previewing: Bool let openMessageFromSearchDisposable: MetaDisposable = MetaDisposable() @@ -175,19 +174,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private weak var emojiStatusSelectionController: ViewController? + private var forumChannelTracker: ForumChannelTopics? + public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) { if self.isNodeLoaded { self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition) } } - public init(context: AccountContext, groupId: PeerGroupId, filter: ChatListFilter? = nil, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool = false, previewing: Bool = false, enableDebugActions: Bool) { + public init(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool = false, previewing: Bool = false, enableDebugActions: Bool) { self.context = context self.controlsHistoryPreload = controlsHistoryPreload self.hideNetworkActivityStatus = hideNetworkActivityStatus - self.groupId = groupId - self.filter = filter + self.location = location self.previewing = previewing self.presentationData = (context.sharedContext.currentPresentationData.with { $0 }) @@ -213,12 +213,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style let title: String - if let filter = self.filter, case let .filter(_, filterTitle, _, _) = filter { - title = filterTitle - } else if self.groupId == .root { - title = self.presentationData.strings.DialogList_Title - } else { - title = self.presentationData.strings.ChatList_ArchivedChatsTitle + switch self.location { + case let .chatList(groupId): + if groupId == .root { + title = self.presentationData.strings.DialogList_Title + } else { + title = self.presentationData.strings.ChatList_ArchivedChatsTitle + } + case let .forum(peerId): + //TODO:localize + title = "Forum" + + self.forumChannelTracker = ForumChannelTopics(account: self.context.account, peerId: peerId) } self.titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil) @@ -229,40 +235,45 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if !previewing { - if self.groupId == .root && self.filter == nil { - self.tabBarItem.title = self.presentationData.strings.DialogList_Title - - let icon: UIImage? - if useSpecialTabBarIcons() { - icon = UIImage(bundleImageName: "Chat List/Tabs/Holiday/IconChats") + switch self.location { + case let .chatList(groupId): + if groupId == .root { + self.tabBarItem.title = self.presentationData.strings.DialogList_Title + + let icon: UIImage? + if useSpecialTabBarIcons() { + icon = UIImage(bundleImageName: "Chat List/Tabs/Holiday/IconChats") + } else { + icon = UIImage(bundleImageName: "Chat List/Tabs/IconChats") + } + + self.tabBarItem.image = icon + self.tabBarItem.selectedImage = icon + if !self.presentationData.reduceMotion { + self.tabBarItem.animationName = "TabChats" + self.tabBarItem.animationOffset = CGPoint(x: 0.0, y: UIScreenPixel) + } + + let leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) + leftBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit + self.navigationItem.leftBarButtonItem = leftBarButtonItem + + let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) + rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose + self.navigationItem.rightBarButtonItem = rightBarButtonItem + let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil) + backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back + self.navigationItem.backBarButtonItem = backBarButtonItem } else { - icon = UIImage(bundleImageName: "Chat List/Tabs/IconChats") + let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) + rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit + self.navigationItem.rightBarButtonItem = rightBarButtonItem + let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back + self.navigationItem.backBarButtonItem = backBarButtonItem } - - self.tabBarItem.image = icon - self.tabBarItem.selectedImage = icon - if !self.presentationData.reduceMotion { - self.tabBarItem.animationName = "TabChats" - self.tabBarItem.animationOffset = CGPoint(x: 0.0, y: UIScreenPixel) - } - - let leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) - leftBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit - self.navigationItem.leftBarButtonItem = leftBarButtonItem - - let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) - rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose - self.navigationItem.rightBarButtonItem = rightBarButtonItem - let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil) - backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back - self.navigationItem.backBarButtonItem = backBarButtonItem - } else { - let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) - rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit - self.navigationItem.rightBarButtonItem = rightBarButtonItem - let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back - self.navigationItem.backBarButtonItem = backBarButtonItem + case .forum: + break } } @@ -329,9 +340,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } let peerStatus: Signal - if filter != nil || groupId != .root { - peerStatus = .single(nil) - } else { + switch self.location { + case .chatList(.root): peerStatus = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> map { peer -> NetworkStatusTitle.Status? in guard case let .user(user) = peer else { @@ -346,6 +356,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } |> distinctUntilChanged + default: + peerStatus = .single(nil) } let previousEditingAndNetworkStateValue = Atomic<(Bool, AccountNetworkState)?>(value: nil) @@ -360,14 +372,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ).start(next: { [weak self] networkState, proxy, passcode, stateAndFilterId, isReorderingTabs, peerStatus in if let strongSelf = self { let defaultTitle: String - if strongSelf.groupId == .root { - defaultTitle = strongSelf.presentationData.strings.DialogList_Title - } else { - defaultTitle = strongSelf.presentationData.strings.ChatList_ArchivedChatsTitle + switch strongSelf.location { + case let .chatList(groupId): + if groupId == .root { + defaultTitle = strongSelf.presentationData.strings.DialogList_Title + } else { + defaultTitle = strongSelf.presentationData.strings.ChatList_ArchivedChatsTitle + } + case .forum: + //TODO:localize + defaultTitle = "Forum" } let previousEditingAndNetworkState = previousEditingAndNetworkStateValue.swap((stateAndFilterId.state.editing, networkState)) if stateAndFilterId.state.editing { - if strongSelf.groupId == .root { + if case .chatList(.root) = strongSelf.location { strongSelf.navigationItem.setRightBarButton(nil, animated: true) } let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? strongSelf.presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle @@ -380,7 +398,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } strongSelf.titleView.setTitle(NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus), animated: animated) } else if isReorderingTabs { - if strongSelf.groupId == .root { + if case .chatList(.root) = strongSelf.location { strongSelf.navigationItem.setRightBarButton(nil, animated: true) } let leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed)) @@ -403,7 +421,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } else { var isRoot = false - if case .root = strongSelf.groupId { + if case .chatList(.root) = strongSelf.location { isRoot = true if isReorderingTabs { @@ -461,7 +479,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController case .online: strongSelf.titleView.setTitle(NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus), animated: (previousEditingAndNetworkState?.0 ?? false) != stateAndFilterId.state.editing) } - if groupId == .root && filter == nil && checkProxy { + if case .chatList(.root) = location, checkProxy { if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil && strongSelf.navigationController?.topViewController === self { strongSelf.didShowProxyUnavailableTooltipController = true let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true) @@ -807,7 +825,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - if self.filter == nil, case .root = self.groupId { + if case .chatList(.root) = self.location { self.chatListDisplayNode.containerNode.currentItemFilterUpdated = { [weak self] filter, fraction, transition, force in guard let strongSelf = self else { return @@ -890,7 +908,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } private func updateThemeAndStrings() { - if case .root = self.groupId { + if case .chatList(.root) = self.location { self.tabBarItem.title = self.presentationData.strings.DialogList_Title let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil) backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back @@ -917,7 +935,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) editItem.accessibilityLabel = self.presentationData.strings.Common_Edit } - if self.groupId == .root && self.filter == nil { + if case .chatList(.root) = self.location { self.navigationItem.leftBarButtonItem = editItem let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose @@ -943,7 +961,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } override public func loadDisplayNode() { - self.displayNode = ChatListControllerNode(context: self.context, groupId: EngineChatList.Group(self.groupId), filter: self.filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, controller: self) + self.displayNode = ChatListControllerNode(context: self.context, location: self.location, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, controller: self) self.chatListDisplayNode.navigationBar = self.navigationBar @@ -994,51 +1012,72 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.deletePeerChat(peerId: peerId, joined: joined) } - self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, animated, activateInput, promoInfo in + self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, threadId, animated, activateInput, promoInfo in if let strongSelf = self { if let navigationController = strongSelf.navigationController as? NavigationController { var scrollToEndIfExists = false if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass { scrollToEndIfExists = true } - - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), activateInput: (activateInput && !peer.isDeleted) ? .text : nil, scrollToEndIfExists: scrollToEndIfExists, animated: !scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, chatListFilter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id, completion: { [weak self] controller in - self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) - if let promoInfo = promoInfo { - switch promoInfo { - case .proxy: - let _ = (ApplicationSpecificNotice.getProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager) - |> deliverOnMainQueue).start(next: { value in - guard let strongSelf = self else { - return - } - if !value { - controller.displayPromoAnnouncement(text: strongSelf.presentationData.strings.DialogList_AdNoticeAlert) - let _ = ApplicationSpecificNotice.setProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager).start() - } - }) - case let .psa(type, _): - let _ = (ApplicationSpecificNotice.getPsaAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id) - |> deliverOnMainQueue).start(next: { value in - guard let strongSelf = self else { - return - } - if !value { - var text = strongSelf.presentationData.strings.ChatList_GenericPsaAlert - let key = "ChatList.PsaAlert.\(type)" - if let string = strongSelf.presentationData.strings.primaryComponent.dict[key] { - text = string - } else if let string = strongSelf.presentationData.strings.secondaryComponent?.dict[key] { - text = string - } - - controller.displayPromoAnnouncement(text: text) - let _ = ApplicationSpecificNotice.setPsaAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start() - } - }) + + if case let .channel(channel) = peer, channel.flags.contains(.isForum), threadId == nil { + strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) + } else { + if let threadId = threadId { + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, navigationController: navigationController).start() + strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) + } else { + var navigationAnimationOptions: NavigationAnimationOptions = [] + var groupId: EngineChatList.Group = .root + if case let .chatList(groupIdValue) = strongSelf.location { + groupId = groupIdValue + if case .root = groupIdValue { + navigationAnimationOptions = .removeOnMasterDetails + } } + + let chatLocation: ChatLocation + chatLocation = .peer(id: peer.id) + + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: chatLocation, activateInput: (activateInput && !peer.isDeleted) ? .text : nil, scrollToEndIfExists: scrollToEndIfExists, animated: !scrollToEndIfExists, options: navigationAnimationOptions, parentGroupId: groupId._asGroup(), chatListFilter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id, completion: { [weak self] controller in + self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) + if let promoInfo = promoInfo { + switch promoInfo { + case .proxy: + let _ = (ApplicationSpecificNotice.getProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { value in + guard let strongSelf = self else { + return + } + if !value { + controller.displayPromoAnnouncement(text: strongSelf.presentationData.strings.DialogList_AdNoticeAlert) + let _ = ApplicationSpecificNotice.setProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager).start() + } + }) + case let .psa(type, _): + let _ = (ApplicationSpecificNotice.getPsaAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id) + |> deliverOnMainQueue).start(next: { value in + guard let strongSelf = self else { + return + } + if !value { + var text = strongSelf.presentationData.strings.ChatList_GenericPsaAlert + let key = "ChatList.PsaAlert.\(type)" + if let string = strongSelf.presentationData.strings.primaryComponent.dict[key] { + text = string + } else if let string = strongSelf.presentationData.strings.secondaryComponent?.dict[key] { + text = string + } + + controller.displayPromoAnnouncement(text: text) + let _ = ApplicationSpecificNotice.setPsaAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start() + } + }) + } + } + })) } - })) + } } } } @@ -1046,7 +1085,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.chatListDisplayNode.containerNode.groupSelected = { [weak self] groupId in if let strongSelf = self { if let navigationController = strongSelf.navigationController as? NavigationController { - let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId._asGroup(), controlsHistoryPreload: false, enableDebugActions: false) + let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .chatList(groupId: groupId), controlsHistoryPreload: false, enableDebugActions: false) chatListController.navigationPresentation = .master navigationController.pushViewController(chatListController) strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) @@ -1081,11 +1120,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass { scrollToEndIfExists = true } + var navigationAnimationOptions: NavigationAnimationOptions = [] + if case .chatList(.root) = strongSelf.location { + navigationAnimationOptions = .removeOnMasterDetails + } strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: actualPeerId), subject: .message(id: .id(messageId), highlight: true, timecode: nil), purposefulAction: { if deactivateOnAction { self?.deactivateSearch(animated: false) } - }, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [])) + }, scrollToEndIfExists: scrollToEndIfExists, options: navigationAnimationOptions)) strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } } @@ -1106,9 +1149,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController scrollToEndIfExists = true } if let navigationController = strongSelf.navigationController as? NavigationController { + var navigationAnimationOptions: NavigationAnimationOptions = [] + if case .chatList(.root) = strongSelf.location { + navigationAnimationOptions = .removeOnMasterDetails + } strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), purposefulAction: { [weak self] in self?.deactivateSearch(animated: false) - }, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [])) + }, scrollToEndIfExists: scrollToEndIfExists, options: navigationAnimationOptions)) strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } } @@ -1211,7 +1258,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } var joined = false - if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { + if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { for media in message.media { if let action = media as? TelegramMediaAction, action.action == .peerJoined { joined = true @@ -1221,11 +1268,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController switch item.content { case let .groupReference(groupId, _, _, _, _): - let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId._asGroup(), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) + let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .chatList(groupId: groupId), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) chatListController.navigationPresentation = .master let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) - case let .peer(_, peer, _, _, _, _, _, _, _, promoInfo, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _): let source: ContextContentSource if let location = location { source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location)) @@ -1300,7 +1347,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } var toolbar: Toolbar? - if case .root = strongSelf.groupId { + if case .chatList(.root) = strongSelf.location { if let (options, peerIds) = peerIdsAndOptions { let leftAction: ToolbarAction switch options.read { @@ -1567,21 +1614,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }))) } - /*if let id = id { - items.append(.action(ContextMenuActionItem(text: "View as Feed", icon: { _ in - return nil - }, action: { c, f in - c.dismiss(completion: { - guard let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController else { - return - } - - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .feed(id: id), subject: nil, purposefulAction: { - }, scrollToEndIfExists: false, options: [])) - }) - }))) - }*/ - let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode, keepInPlace: keepInPlace)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) }) @@ -1593,10 +1625,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController tabContextGesture(id, sourceNode, gesture, true, isDisabled) } - if case .group = self.groupId { - self.ready.set(self.chatListDisplayNode.containerNode.ready) - } else { + if case .chatList(.root) = self.location { self.ready.set(.never()) + } else { + self.ready.set(self.chatListDisplayNode.containerNode.ready) } self.displayNodeDidLoad() @@ -1653,7 +1685,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.push(controller) } - guard case .root = self.groupId else { + guard case .chatList(.root) = self.location else { return } @@ -1988,9 +2020,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } @objc private func editPressed() { + if case .forum = self.location { + self.forumChannelTracker?.createTopic(title: "Topic#\(Int.random(in: 0 ..< 100000))") + return + } + let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) editItem.accessibilityLabel = self.presentationData.strings.Common_Done - if case .root = self.groupId, self.filter == nil { + if case .chatList(.root) = self.location { self.navigationItem.setLeftBarButton(editItem, animated: true) (self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(.details, transition: .animated(duration: 0.5, curve: .spring)) } else { @@ -2122,7 +2159,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } var resolvedItems = filterItems - if strongSelf.groupId != .root { + if case .chatList(.root) = strongSelf.location { + } else { resolvedItems = [] } @@ -2578,8 +2616,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self?.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil) } signal = self.context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: Array(peerIds), setToValue: false) - } else { - let groupId = self.groupId + } else if case let .chatList(groupId) = self.location { let filterPredicate: ChatListFilterPredicate? if let filter = self.chatListDisplayNode.containerNode.currentItemNode.chatListFilter, case let .filter(_, _, _, data) = filter { filterPredicate = chatListFilterPredicate(filter: data) @@ -2587,13 +2624,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController filterPredicate = nil } var markItems: [(groupId: EngineChatList.Group, filterPredicate: ChatListFilterPredicate?)] = [] - markItems.append((EngineChatList.Group(groupId), filterPredicate)) + markItems.append((groupId, filterPredicate)) if let filterPredicate = filterPredicate { for additionalGroupId in filterPredicate.includeAdditionalPeerGroupIds { markItems.append((EngineChatList.Group(additionalGroupId), filterPredicate)) } } signal = self.context.engine.messages.markAllChatsAsReadInteractively(items: markItems) + } else { + signal = .complete() } let _ = (signal |> deliverOnMainQueue).start(completed: { [weak self] in @@ -2685,7 +2724,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ]) self.present(actionSheet, in: .window(.root)) } else if case .middle = action, !peerIds.isEmpty { - if case .root = self.groupId { + if case .chatList(.root) = self.location { self.donePressed() self.archiveChats(peerIds: Array(peerIds)) } else { @@ -3345,7 +3384,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } override public func setToolbar(_ toolbar: Toolbar?, transition: ContainedViewLayoutTransition) { - if case .root = self.groupId, self.filter == nil { + if case .chatList(.root) = self.location { super.setToolbar(toolbar, transition: transition) } else { self.chatListDisplayNode.toolbar = toolbar diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 82602e6a98..4e04159d39 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -182,8 +182,8 @@ private final class ChatListShimmerNode: ASDisplayNode { let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil)) let timestamp1: Int32 = 100000 let peers: [EnginePeer.Id: EnginePeer] = [:] - let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in + let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) @@ -213,7 +213,7 @@ private final class ChatListShimmerNode: ASDisplayNode { ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -296,7 +296,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { private var validLayout: (CGSize, UIEdgeInsets, CGFloat)? - init(context: AccountContext, groupId: EngineChatList.Group, filter: ChatListFilter?, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void) { + init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void) { self.context = context self.animationCache = animationCache self.animationRenderer = animationRenderer @@ -304,7 +304,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { self.becameEmpty = becameEmpty self.emptyAction = emptyAction - self.listNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true) + self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true) super.init() @@ -426,7 +426,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private let context: AccountContext - private let groupId: EngineChatList.Group + private let location: ChatListControllerLocation private let previewing: Bool private let controlsHistoryPreload: Bool private let filterBecameEmpty: (ChatListFilter?) -> Void @@ -523,8 +523,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { itemNode.listNode.deletePeerChat = { [weak self] peerId, joined in self?.deletePeerChat?(peerId, joined) } - itemNode.listNode.peerSelected = { [weak self] peerId, animated, activateInput, promoInfo in - self?.peerSelected?(peerId, animated, activateInput, promoInfo) + itemNode.listNode.peerSelected = { [weak self] peerId, threadId, animated, activateInput, promoInfo in + self?.peerSelected?(peerId, threadId, animated, activateInput, promoInfo) } itemNode.listNode.groupSelected = { [weak self] groupId in self?.groupSelected?(groupId) @@ -581,7 +581,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { var toggleArchivedFolderHiddenByDefault: (() -> Void)? var hidePsa: ((EnginePeer.Id) -> Void)? var deletePeerChat: ((EnginePeer.Id, Bool) -> Void)? - var peerSelected: ((EnginePeer, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? + var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? @@ -591,9 +591,9 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { var didBeginSelectingChats: (() -> Void)? var displayFilterLimit: (() -> Void)? - init(context: AccountContext, groupId: EngineChatList.Group, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void) { + init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void) { self.context = context - self.groupId = groupId + self.location = location self.previewing = previewing self.filterBecameEmpty = filterBecameEmpty self.filterEmptyAction = filterEmptyAction @@ -607,7 +607,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { super.init() - let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: nil, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: nil, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -883,7 +883,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { itemNode.emptyNode?.restartAnimation() completion?() } else if self.pendingItemNode == nil { - let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[index].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[index].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -1011,7 +1011,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { validNodeIds.append(id) if self.itemNodes[id] == nil && self.enableAdjacentFilterLoading && !self.disableItemNodeOperationsWhileAnimating { - let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[i].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[i].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -1073,7 +1073,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { final class ChatListControllerNode: ASDisplayNode { private let context: AccountContext - private let groupId: EngineChatList.Group + private let location: ChatListControllerLocation private var presentationData: PresentationData private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer @@ -1109,16 +1109,16 @@ final class ChatListControllerNode: ASDisplayNode { let debugListView = ListView() - init(context: AccountContext, groupId: EngineChatList.Group, filter: ChatListFilter?, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, controller: ChatListControllerImpl) { + init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, controller: ChatListControllerImpl) { self.context = context - self.groupId = groupId + self.location = location self.presentationData = presentationData self.animationCache = animationCache self.animationRenderer = animationRenderer var filterBecameEmpty: ((ChatListFilter?) -> Void)? var filterEmptyAction: ((ChatListFilter?) -> Void)? - self.containerNode = ChatListContainerNode(context: context, groupId: groupId, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in + self.containerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in filterBecameEmpty?(filter) }, filterEmptyAction: { filter in filterEmptyAction?(filter) @@ -1145,7 +1145,7 @@ final class ChatListControllerNode: ASDisplayNode { guard let strongSelf = self else { return } - if case .archive = strongSelf.groupId { + if case .chatList(.archive) = strongSelf.location { strongSelf.dismissSelfIfCompletedPresentation?() } } @@ -1258,10 +1258,13 @@ final class ChatListControllerNode: ASDisplayNode { guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else { return nil } + guard case let .chatList(groupId) = self.location else { + return nil + } let filter: ChatListNodePeersFilter = [] - let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, groupId: self.groupId, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, dismissSearch in + let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, groupId: groupId, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, dismissSearch in self?.requestOpenPeerFromSearch?(peer, dismissSearch) }, openDisabledPeer: { _ in }, openRecentPeerOptions: { [weak self] peer in diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index d58dd1da43..04572586cc 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -505,9 +505,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in if case let .peer(maybePeer, maybeChatPeer) = contactPeer, let peer = maybePeer, let chatPeer = maybeChatPeer { - interaction.peerSelected(chatPeer, peer, nil) + interaction.peerSelected(chatPeer, nil, peer, nil) } else { - interaction.peerSelected(peer, nil, nil) + interaction.peerSelected(peer, nil, nil, nil) } }, contextAction: peerContextAction.flatMap { peerContextAction in return { node, gesture, location in @@ -592,9 +592,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in if case let .peer(maybePeer, maybeChatPeer) = contactPeer, let peer = maybePeer, let chatPeer = maybeChatPeer { - interaction.peerSelected(chatPeer, peer, nil) + interaction.peerSelected(chatPeer, nil, peer, nil) } else { - interaction.peerSelected(peer, nil, nil) + interaction.peerSelected(peer, nil, nil, nil) } }, contextAction: peerContextAction.flatMap { peerContextAction in return { node, gesture, location in @@ -658,7 +658,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in - interaction.peerSelected(EnginePeer(peer.peer), nil, nil) + interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil) }, contextAction: peerContextAction.flatMap { peerContextAction in return { node, gesture, location in peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location) @@ -694,7 +694,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { if isMedia { return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(id: peer.peerId), interaction: listInteraction, message: message._asMessage(), selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: key == .downloads ? header : nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: key != .downloads, isDownloadList: key == .downloads) } else { - return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)), content: .peer(messages: [message], peer: peer, threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -1718,7 +1718,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } let chatListInteraction = ChatListNodeInteraction(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: { - }, peerSelected: { [weak self] peer, chatPeer, _ in + }, peerSelected: { [weak self] peer, _, chatPeer, _ in interaction.dismissInput() interaction.openPeer(peer, chatPeer, false) let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).start() @@ -1727,7 +1727,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { [weak self] peer, message, _ in + }, messageSelected: { [weak self] peer, _, message, _ in interaction.dismissInput() if let strongSelf = self, let peer = message.peers[message.id.peerId] { interaction.openMessage(EnginePeer(peer), message.id, strongSelf.key == .chats) @@ -1752,7 +1752,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _): if let peer = peer.peer, let message = messages.first { peerContextAction(peer, .search(message.id), node, gesture, location) } @@ -2794,7 +2794,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { bounds = selectedItemNode.bounds } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _): return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId) case let .groupReference(groupId, _, _, _, _): return (selectedItemNode.view, bounds, groupId) @@ -2950,8 +2950,8 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { let timestamp1: Int32 = 100000 var peers: [EnginePeer.Id: EnginePeer] = [:] peers[peer1.id] = peer1 - let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in + let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) @@ -2982,7 +2982,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { associatedMedia: [:] ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 392568bddb..5efac4fc29 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -49,12 +49,12 @@ public enum ChatListItemContent { } } - case peer(messages: [EngineMessage], peer: EngineRenderedPeer, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool) + case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThreads.Info?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool) case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool) public var chatLocation: ChatLocation? { switch self { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _): return .peer(id: peer.peerId) case .groupReference: return nil @@ -65,7 +65,7 @@ public enum ChatListItemContent { public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { let presentationData: ChatListPresentationData let context: AccountContext - let peerGroupId: EngineChatList.Group + let chatListLocation: ChatListControllerLocation let filterData: ChatListItemFilterData? let index: EngineChatList.Item.Index public let content: ChatListItemContent @@ -85,12 +85,17 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { let header: ListViewItemHeader? public var isPinned: Bool { - return self.index.pinningIndex != nil + switch self.index { + case let .chatList(index): + return index.pinningIndex != nil + case .forum: + return false + } } - public init(presentationData: ChatListPresentationData, context: AccountContext, peerGroupId: EngineChatList.Group, filterData: ChatListItemFilterData?, index: EngineChatList.Item.Index, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { + public init(presentationData: ChatListPresentationData, context: AccountContext, chatListLocation: ChatListControllerLocation, filterData: ChatListItemFilterData?, index: EngineChatList.Item.Index, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { self.presentationData = presentationData - self.peerGroupId = peerGroupId + self.chatListLocation = chatListLocation self.filterData = filterData self.context = context self.index = index @@ -153,13 +158,17 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { public func selected(listView: ListView) { switch self.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, promoInfo, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _): if let message = messages.last, let peer = peer.peer { - self.interaction.messageSelected(peer, message, promoInfo) + var threadId: Int64? + if case let .forum(_, threadIdValue, _, _) = self.index { + threadId = threadIdValue + } + self.interaction.messageSelected(peer, threadId, message, promoInfo) } else if let peer = peer.peer { - self.interaction.peerSelected(peer, nil, promoInfo) + self.interaction.peerSelected(peer, nil, nil, promoInfo) } else if let peer = peer.peers[peer.peerId] { - self.interaction.peerSelected(peer, nil, promoInfo) + self.interaction.peerSelected(peer, nil, nil, promoInfo) } case let .groupReference(groupId, _, _, _, _): self.interaction.groupSelected(groupId) @@ -184,7 +193,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { } var nextIsPinned = false if let nextItem = nextItem as? ChatListItem { - if nextItem.index.pinningIndex != nil { + if case let .chatList(nextIndex) = nextItem.index, nextIndex.pinningIndex != nil { nextIsPinned = true } } else { @@ -242,10 +251,11 @@ public struct ChatListItemFilterData: Equatable { } } -private func revealOptions(strings: PresentationStrings, theme: PresentationTheme, isPinned: Bool, isMuted: Bool?, groupId: EngineChatList.Group, peerId: EnginePeer.Id, accountPeerId: EnginePeer.Id, canDelete: Bool, isEditing: Bool, filterData: ChatListItemFilterData?) -> [ItemListRevealOption] { +private func revealOptions(strings: PresentationStrings, theme: PresentationTheme, isPinned: Bool, isMuted: Bool?, location: ChatListControllerLocation, peerId: EnginePeer.Id, accountPeerId: EnginePeer.Id, canDelete: Bool, isEditing: Bool, filterData: ChatListItemFilterData?) -> [ItemListRevealOption] { var options: [ItemListRevealOption] = [] if !isEditing { - if case .archive = groupId { + if case .forum = location { + } else if case .chatList(.archive) = location { if isPinned { options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) } else { @@ -272,10 +282,12 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem canArchive = true } } else { - if case .root = groupId { - canArchive = true - } else { - canUnarchive = true + if case let .chatList(groupId) = location { + if case .root = groupId { + canArchive = true + } else { + canUnarchive = true + } } } if canArchive { @@ -301,8 +313,8 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr return options } -private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool, isEditing: Bool, isPinned: Bool, isSavedMessages: Bool, groupId: EngineChatList.Group, peer: EnginePeer, filterData: ChatListItemFilterData?) -> [ItemListRevealOption] { - if case .archive = groupId { +private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool, isEditing: Bool, isPinned: Bool, isSavedMessages: Bool, location: ChatListControllerLocation, peer: EnginePeer, filterData: ChatListItemFilterData?) -> [ItemListRevealOption] { + guard case .chatList(.root) = location else { return [] } var options: [ItemListRevealOption] = [] @@ -439,6 +451,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let contextContainer: ContextControllerSourceNode let avatarNode: AvatarNode + var avatarIconView: ComponentHostView? + var avatarIconComponent: EmojiStatusComponent? var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? private let playbackStartDisposable = MetaDisposable() @@ -525,7 +539,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { result += "\n\(item.presentationData.strings.VoiceOver_Chat_UnreadMessages(Int32(allCount)))" } return result - case let .peer(_, peer, combinedReadState, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _): guard let chatMainPeer = peer.chatMainPeer else { return nil } @@ -585,7 +599,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { return item.presentationData.strings.VoiceOver_ChatList_MessageEmpty } - case let .peer(messages, peer, combinedReadState, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _): if let message = messages.last { var result = "" if message.flags.contains(.Incoming) { @@ -644,6 +658,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { containerSize: credibilityIconView.bounds.size ) } + if let avatarIconView = self.avatarIconView, let avatarIconComponent = self.avatarIconComponent { + let _ = avatarIconView.update( + transition: .immediate, + component: AnyComponent(avatarIconComponent.withVisibleForAnimations(self.visibilityStatus)), + environment: {}, + containerSize: avatarIconView.bounds.size + ) + } } } } @@ -770,7 +792,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var displayAsMessage = false var enablePreview = true switch item.content { - case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _): + case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _): displayAsMessage = displayAsMessageValue if displayAsMessage, case let .user(author) = messages.last?.author { peer = .user(author) @@ -922,7 +944,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let item = self.item { let onlineIcon: UIImage? - if item.index.pinningIndex != nil { + if case let .chatList(index) = item.index, index.pinningIndex != nil { onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: self.onlineIsVoiceChat) } else { onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: self.onlineIsVoiceChat) @@ -936,7 +958,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { guard let item = self.item, item.editing else { return } - if case let .peer(_, peer, _, _, _, _, _, _, _, promoInfo, _, _, _) = item.content { + if case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _) = item.content { if promoInfo == nil, let mainPeer = peer.peer { item.interaction.togglePeerSelected(mainPeer) } @@ -985,11 +1007,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let promoInfo: ChatListNodeEntryPromoInfo? let displayAsMessage: Bool let hasFailedMessages: Bool + var threadInfo: EngineMessageHistoryThreads.Info? var groupHiddenByDefault = false switch item.content { - case let .peer(messagesValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _): + case let .peer(messagesValue, peerValue, threadInfoValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _): messages = messagesValue contentPeer = .chat(peerValue) combinedReadState = combinedReadStateValue @@ -1007,6 +1030,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { return EnginePeer.Presence(status: presence.status, lastActivity: 0) } draftState = draftStateValue + threadInfo = threadInfoValue hasUnseenMentions = hasUnseenMentionsValue hasUnseenReactions = hasUnseenReactionsValue @@ -1095,7 +1119,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } editingOffset = sizeAndApply.0 - if item.index.pinningIndex != nil && promoInfo == nil && !isPeerGroup { + if case let .chatList(index) = item.index, index.pinningIndex != nil, promoInfo == nil, !isPeerGroup { let sizeAndApply = reorderControlLayout(item.presentationData.theme) reorderControlSizeAndApply = sizeAndApply reorderInset = sizeAndApply.0 @@ -1107,14 +1131,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let enableChatListPhotos = true let avatarDiameter = min(60.0, floor(item.presentationData.fontSize.baseDisplaySize * 60.0 / 17.0)) - let avatarLeftInset = 18.0 + avatarDiameter + + let avatarLeftInset: CGFloat + if case .forum = item.index { + avatarLeftInset = 56.0 + } else { + avatarLeftInset = 18.0 + avatarDiameter + } let badgeDiameter = floor(item.presentationData.fontSize.baseDisplaySize * 20.0 / 17.0) let leftInset: CGFloat = params.leftInset + avatarLeftInset enum ContentData { - case chat(itemPeer: EngineRenderedPeer, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) + case chat(itemPeer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThreads.Info?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) case group(peers: [EngineChatList.GroupItem.Item]) } @@ -1149,7 +1179,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { break } - contentData = .chat(itemPeer: itemPeer, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers, customEmojiRanges: customEmojiRanges) + contentData = .chat(itemPeer: itemPeer, threadInfo: threadInfo, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers, customEmojiRanges: customEmojiRanges) hideAuthor = initialHideAuthor case let .group(groupPeers): contentData = .group(peers: groupPeers) @@ -1180,7 +1210,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var contentImageSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = [] switch contentData { - case let .chat(itemPeer, _, _, text, spoilers, customEmojiRanges): + case let .chat(itemPeer, _, _, _, text, spoilers, customEmojiRanges): var isUser = false if case .user = itemPeer.chatMainPeer { isUser = true @@ -1191,7 +1221,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let messagePeer = itemPeer.chatMainPeer { peerText = messagePeer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } - } else if let message = messages.last, let author = message.author?._asPeer(), let peer = itemPeer.chatMainPeer, !isUser { + } else if let message = messages.last, let author = message.author?._asPeer(), let peer = itemPeer.chatMainPeer, !isUser { if case let .channel(peer) = peer, case .broadcast = peer.info { } else if !displayAsMessage { if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature { @@ -1390,8 +1420,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } switch contentData { - case let .chat(itemPeer, _, _, _, _, _): - if let message = messages.last, case let .user(author) = message.author, displayAsMessage { + case let .chat(itemPeer, threadInfo, _, _, _, _, _): + if let threadInfo = threadInfo { + titleAttributedString = NSAttributedString(string: threadInfo.title, font: titleFont, textColor: theme.titleColor) + } else if let message = messages.last, case let .user(author) = message.author, displayAsMessage { titleAttributedString = NSAttributedString(string: author.id == account.peerId ? item.presentationData.strings.DialogList_You : EnginePeer.user(author).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: titleFont, textColor: theme.titleColor) } else if isPeerGroup { titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor) @@ -1400,7 +1432,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else if let id = itemPeer.chatMainPeer?.id, id.isReplies { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleFont, textColor: theme.titleColor) } else if let displayTitle = itemPeer.chatMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) { - titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: item.index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat ? theme.secretTitleColor : theme.titleColor) + let textColor: UIColor + if case let .chatList(index) = item.index, index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat { + textColor = theme.secretTitleColor + } else { + textColor = theme.titleColor + } + titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: textColor) } case .group: titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor) @@ -1408,12 +1446,25 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { textAttributedString = attributedText - var t = Int(item.index.messageIndex.timestamp) - var timeinfo = tm() - localtime_r(&t, &timeinfo) - - let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.index.messageIndex.timestamp, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat) + let dateText: String + var topIndex: MessageIndex? + switch item.content { + case let .groupReference(_, _, message, _, _): + topIndex = message?.index + case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _): + topIndex = messages.first?.index + } + if let topIndex { + var t = Int(topIndex.timestamp) + var timeinfo = tm() + localtime_r(&t, &timeinfo) + + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + + dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: topIndex.timestamp, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat) + } else { + dateText = "" + } if isPeerGroup { dateAttributedString = NSAttributedString(string: "", font: dateFont, textColor: theme.dateTextColor) @@ -1481,7 +1532,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if !isPeerGroup { if hasUnseenMentions { - if case .archive = item.peerGroupId { + if case .chatList(.archive) = item.chatListLocation { currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundInactiveMention(item.presentationData.theme, diameter: badgeDiameter) } else { currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundMention(item.presentationData.theme, diameter: badgeDiameter) @@ -1494,7 +1545,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundReactions(item.presentationData.theme, diameter: badgeDiameter) } mentionBadgeContent = .mention - } else if item.index.pinningIndex != nil && promoInfo == nil && currentBadgeBackgroundImage == nil { + } else if case let .chatList(chatListIndex) = item.index, chatListIndex.pinningIndex != nil, promoInfo == nil, currentBadgeBackgroundImage == nil { currentPinnedIconImage = PresentationResourcesChatList.badgeBackgroundPinned(item.presentationData.theme, diameter: badgeDiameter) } } @@ -1519,16 +1570,25 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { titleIconsWidth += currentMutedIconImage.size.width } - let isSecret = !isPeerGroup && item.index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat + var isSecret = false + if !isPeerGroup { + if case let .chatList(index) = item.index, index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat { + isSecret = true + } + } if isSecret { currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme) } let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) - if !isPeerGroup && item.index.messageIndex.id.peerId != item.context.account.peerId { + var isAccountPeer = false + if case let .chatList(index) = item.index, index.messageIndex.id.peerId == item.context.account.peerId { + isAccountPeer = true + } + if !isPeerGroup && !isAccountPeer { if displayAsMessage { switch item.content { - case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _): if let peer = messages.last?.author { if case let .user(user) = peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled { currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) @@ -1623,13 +1683,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var inputActivitiesSize: CGSize? var inputActivitiesApply: (() -> Void)? - if let inputActivities = inputActivities, !inputActivities.isEmpty { - - let (size, apply) = inputActivitiesLayout(CGSize(width: rawContentWidth - badgeSize, height: 40.0), item.presentationData, item.presentationData.theme.chatList.messageTextColor, item.index.messageIndex.id.peerId, inputActivities) + if let inputActivities = inputActivities, !inputActivities.isEmpty, case let .chatList(index) = item.index { + let (size, apply) = inputActivitiesLayout(CGSize(width: rawContentWidth - badgeSize, height: 40.0), item.presentationData, item.presentationData.theme.chatList.messageTextColor, index.messageIndex.id.peerId, inputActivities) inputActivitiesSize = size inputActivitiesApply = apply } else { - let (size, apply) = inputActivitiesLayout(CGSize(width: rawContentWidth - badgeSize, height: 40.0), item.presentationData, item.presentationData.theme.chatList.messageTextColor, item.index.messageIndex.id.peerId, []) + let (size, apply) = inputActivitiesLayout(CGSize(width: rawContentWidth - badgeSize, height: 40.0), item.presentationData, item.presentationData.theme.chatList.messageTextColor, nil, []) inputActivitiesSize = size inputActivitiesApply = apply } @@ -1637,14 +1696,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var online = false var animateOnline = false var onlineIsVoiceChat = false + + var isPinned = false + if case let .chatList(index) = item.index { + isPinned = index.pinningIndex != nil + } let peerRevealOptions: [ItemListRevealOption] let peerLeftRevealOptions: [ItemListRevealOption] switch item.content { - case let .peer(_, renderedPeer, _, _, presence, _, _, _, _, _, _, displayAsMessage, _): + case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _): if !displayAsMessage { if case let .user(peer) = renderedPeer.chatMainPeer, let presence = presence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId { let updatedPresence = EnginePeer.Presence(status: presence.status, lastActivity: 0) + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) let relativeStatus = relativeUserPresenceStatus(updatedPresence, relativeTo: timestamp) if case .online = relativeStatus { online = true @@ -1665,8 +1730,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } - let isPinned = item.index.pinningIndex != nil - if item.enableContextActions { if case .psa = promoInfo { peerRevealOptions = [ @@ -1674,9 +1737,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { ] peerLeftRevealOptions = [] } else if promoInfo == nil { - peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: item.context.account.peerId != item.index.messageIndex.id.peerId ? (currentMutedIconImage != nil) : nil, groupId: item.peerGroupId, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing, filterData: item.filterData) + peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: !isAccountPeer ? (currentMutedIconImage != nil) : nil, location: item.chatListLocation, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing, filterData: item.filterData) if case let .chat(itemPeer) = contentPeer { - peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, groupId: item.peerGroupId, peer: itemPeer.peers[itemPeer.peerId]!, filterData: item.filterData) + peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, location: item.chatListLocation, peer: itemPeer.peers[itemPeer.peerId]!, filterData: item.filterData) } else { peerLeftRevealOptions = [] } @@ -1813,10 +1876,50 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition.updateAlpha(node: strongSelf.statusNode, alpha: 1.0) } + let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0) + let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame) strongSelf.updateVideoVisibility() + if let iconFileId = threadInfo?.icon { + let avatarIconView: ComponentHostView + if let current = strongSelf.avatarIconView { + avatarIconView = current + } else { + avatarIconView = ComponentHostView() + strongSelf.avatarIconView = avatarIconView + strongSelf.contextContainer.view.addSubview(avatarIconView) + } + + let avatarIconComponent = EmojiStatusComponent( + context: item.context, + animationCache: item.interaction.animationCache, + animationRenderer: item.interaction.animationRenderer, + content: .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: 40.0, height: 40.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever), + isVisibleForAnimations: strongSelf.visibilityStatus, + action: nil + ) + strongSelf.avatarIconComponent = avatarIconComponent + + let iconSize = avatarIconView.update( + transition: .immediate, + component: AnyComponent(avatarIconComponent), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + transition.updateFrame(view: avatarIconView, frame: CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0), y: contentRect.origin.y + 2.0), size: iconSize)) + } else if let avatarIconView = strongSelf.avatarIconView { + strongSelf.avatarIconView = nil + avatarIconView.removeFromSuperview() + } + + if case .forum = item.index { + strongSelf.avatarNode.isHidden = true + } else { + strongSelf.avatarNode.isHidden = false + } + let onlineFrame: CGRect if onlineIsVoiceChat { onlineFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - onlineLayout.width + 1.0 - UIScreenPixel, y: avatarFrame.maxY - onlineLayout.height + 1.0 - UIScreenPixel), size: onlineLayout) @@ -1828,7 +1931,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let onlineIcon: UIImage? if strongSelf.reallyHighlighted { onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .highlighted, voiceChat: onlineIsVoiceChat) - } else if item.index.pinningIndex != nil { + } else if case let .chatList(index) = item.index, index.pinningIndex != nil { onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: onlineIsVoiceChat) } else { onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: onlineIsVoiceChat) @@ -1852,8 +1955,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let _ = mentionBadgeApply(animateBadges, true) let _ = onlineApply(animateContent && animateOnline) - let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0) - transition.updateFrame(node: strongSelf.dateNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateLayout.size.width, y: contentRect.origin.y + 2.0), size: dateLayout.size)) let statusSize = CGSize(width: 24.0, height: 24.0) @@ -2104,7 +2205,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let separatorInset: CGFloat if case let .groupReference(_, _, _, _, hiddenByDefault) = item.content, hiddenByDefault { separatorInset = 0.0 - } else if (!nextIsPinned && item.index.pinningIndex != nil) || last { + } else if (!nextIsPinned && isPinned) || last { separatorInset = 0.0 } else { separatorInset = editingOffset + leftInset + rawContentRect.origin.x @@ -2118,7 +2219,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if item.selected { backgroundColor = theme.itemSelectedBackgroundColor highlightedBackgroundColor = theme.itemHighlightedBackgroundColor - } else if item.index.pinningIndex != nil { + } else if isPinned { if case let .groupReference(_, _, _, _, hiddenByDefault) = item.content, hiddenByDefault { backgroundColor = theme.itemBackgroundColor highlightedBackgroundColor = theme.itemHighlightedBackgroundColor @@ -2398,90 +2499,100 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { override func revealOptionsInteractivelyOpened() { if let item = self.item { - item.interaction.setPeerIdWithRevealedOptions(item.index.messageIndex.id.peerId, nil) + switch item.index { + case let .chatList(index): + item.interaction.setPeerIdWithRevealedOptions(index.messageIndex.id.peerId, nil) + case .forum: + break + } } } override func revealOptionsInteractivelyClosed() { if let item = self.item { - item.interaction.setPeerIdWithRevealedOptions(nil, item.index.messageIndex.id.peerId) + switch item.index { + case let .chatList(index): + item.interaction.setPeerIdWithRevealedOptions(nil, index.messageIndex.id.peerId) + case .forum: + break + } } } override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { var close = true - if let item = self.item { + if let item = self.item, case let .chatList(index) = item.index { switch option.key { - case RevealOptionKey.pin.rawValue: - switch item.content { - case .peer: - let itemId: EngineChatList.PinnedItem.Id = .peer(item.index.messageIndex.id.peerId) - item.interaction.setItemPinned(itemId, true) - case .groupReference: - break - } - case RevealOptionKey.unpin.rawValue: - switch item.content { - case .peer: - let itemId: EngineChatList.PinnedItem.Id = .peer(item.index.messageIndex.id.peerId) - item.interaction.setItemPinned(itemId, false) - case .groupReference: - break - } - case RevealOptionKey.mute.rawValue: - item.interaction.setPeerMuted(item.index.messageIndex.id.peerId, true) - close = false - case RevealOptionKey.unmute.rawValue: - item.interaction.setPeerMuted(item.index.messageIndex.id.peerId, false) - close = false - case RevealOptionKey.delete.rawValue: - var joined = false - if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { - for media in message.media { - if let action = media as? TelegramMediaAction, action.action == .peerJoined { - joined = true - } + case RevealOptionKey.pin.rawValue: + switch item.content { + case .peer: + let itemId: EngineChatList.PinnedItem.Id = .peer(index.messageIndex.id.peerId) + item.interaction.setItemPinned(itemId, true) + case .groupReference: + break + } + case RevealOptionKey.unpin.rawValue: + switch item.content { + case .peer: + let itemId: EngineChatList.PinnedItem.Id = .peer(index.messageIndex.id.peerId) + item.interaction.setItemPinned(itemId, false) + case .groupReference: + break + } + case RevealOptionKey.mute.rawValue: + item.interaction.setPeerMuted(index.messageIndex.id.peerId, true) + close = false + case RevealOptionKey.unmute.rawValue: + item.interaction.setPeerMuted(index.messageIndex.id.peerId, false) + close = false + case RevealOptionKey.delete.rawValue: + var joined = false + if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { + for media in message.media { + if let action = media as? TelegramMediaAction, action.action == .peerJoined { + joined = true } } - item.interaction.deletePeer(item.index.messageIndex.id.peerId, joined) - case RevealOptionKey.archive.rawValue: - item.interaction.updatePeerGrouping(item.index.messageIndex.id.peerId, true) - close = false - self.skipFadeout = true - self.animateRevealOptionsFill { - self.revealOptionsInteractivelyClosed() - } - case RevealOptionKey.unarchive.rawValue: - item.interaction.updatePeerGrouping(item.index.messageIndex.id.peerId, false) - close = false - self.skipFadeout = true - self.animateRevealOptionsFill { - self.revealOptionsInteractivelyClosed() - } - case RevealOptionKey.toggleMarkedUnread.rawValue: - item.interaction.togglePeerMarkedUnread(item.index.messageIndex.id.peerId, animated) - close = false - case RevealOptionKey.hide.rawValue: - item.interaction.toggleArchivedFolderHiddenByDefault() - close = false - self.skipFadeout = true - self.animateRevealOptionsFill { - self.revealOptionsInteractivelyClosed() - } - case RevealOptionKey.unhide.rawValue: - item.interaction.toggleArchivedFolderHiddenByDefault() - close = false - case RevealOptionKey.hidePsa.rawValue: - if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _) = item.content { - item.interaction.hidePsa(peer.peerId) - } - close = false - self.skipFadeout = true - self.animateRevealOptionsFill { - self.revealOptionsInteractivelyClosed() - } - default: - break + } + item.interaction.deletePeer(index.messageIndex.id.peerId, joined) + case RevealOptionKey.archive.rawValue: + item.interaction.updatePeerGrouping(index.messageIndex.id.peerId, true) + close = false + self.skipFadeout = true + self.animateRevealOptionsFill { + self.revealOptionsInteractivelyClosed() + } + case RevealOptionKey.unarchive.rawValue: + item.interaction.updatePeerGrouping(index.messageIndex.id.peerId, false) + close = false + self.skipFadeout = true + self.animateRevealOptionsFill { + self.revealOptionsInteractivelyClosed() + } + case RevealOptionKey.toggleMarkedUnread.rawValue: + item.interaction.togglePeerMarkedUnread(index.messageIndex.id.peerId, animated) + close = false + case RevealOptionKey.hide.rawValue: + item.interaction.toggleArchivedFolderHiddenByDefault() + close = false + self.skipFadeout = true + self.animateRevealOptionsFill { + self.revealOptionsInteractivelyClosed() + } + case RevealOptionKey.unhide.rawValue: + item.interaction.toggleArchivedFolderHiddenByDefault() + close = false + case RevealOptionKey.hidePsa.rawValue: + if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _) = item.content { + item.interaction.hidePsa(peer.peerId) + } + close = false + self.skipFadeout = true + self.animateRevealOptionsFill { + self.revealOptionsInteractivelyClosed() + } + default: + break } } if close { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index bb61c8a6e7..bcd0013910 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -56,12 +56,12 @@ public final class ChatListNodeInteraction { } let activateSearch: () -> Void - let peerSelected: (EnginePeer, EnginePeer?, ChatListNodeEntryPromoInfo?) -> Void + let peerSelected: (EnginePeer, Int64?, EnginePeer?, ChatListNodeEntryPromoInfo?) -> Void let disabledPeerSelected: (EnginePeer) -> Void let togglePeerSelected: (EnginePeer) -> Void let togglePeersSelection: ([PeerEntry], Bool) -> Void let additionalCategorySelected: (Int) -> Void - let messageSelected: (EnginePeer, EngineMessage, ChatListNodeEntryPromoInfo?) -> Void + let messageSelected: (EnginePeer, Int64?, EngineMessage, ChatListNodeEntryPromoInfo?) -> Void let groupSelected: (EngineChatList.Group) -> Void let addContact: (String) -> Void let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void @@ -86,12 +86,12 @@ public final class ChatListNodeInteraction { animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, activateSearch: @escaping () -> Void, - peerSelected: @escaping (EnginePeer, EnginePeer?, ChatListNodeEntryPromoInfo?) -> Void, + peerSelected: @escaping (EnginePeer, Int64?, EnginePeer?, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (EnginePeer) -> Void, togglePeerSelected: @escaping (EnginePeer) -> Void, togglePeersSelection: @escaping ([PeerEntry], Bool) -> Void, additionalCategorySelected: @escaping (Int) -> Void, - messageSelected: @escaping (EnginePeer, EngineMessage, ChatListNodeEntryPromoInfo?) -> Void, + messageSelected: @escaping (EnginePeer, Int64?, EngineMessage, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (EngineChatList.Group) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, @@ -219,7 +219,7 @@ public struct ChatListNodeState: Equatable { } } -private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: EngineChatList.Group, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { +private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { return entries.map { entry -> ListViewInsertItem in switch entry.entry { case .HeaderEntry: @@ -242,18 +242,19 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction.additionalCategorySelected(id) } ), directionHint: entry.directionHint) - case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): switch mode { case .chatList: return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: presentationData, context: context, - peerGroupId: peerGroupId, + chatListLocation: location, filterData: filterData, index: index, content: .peer( messages: messages, peer: peer, + threadInfo: threadInfo, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, @@ -386,7 +387,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL if editing { nodeInteraction.togglePeerSelected(chatPeer) } else { - nodeInteraction.peerSelected(chatPeer, nil, nil) + nodeInteraction.peerSelected(chatPeer, nil, nil, nil) } } }, disabledAction: { _ in @@ -404,7 +405,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: presentationData, context: context, - peerGroupId: peerGroupId, + chatListLocation: location, filterData: filterData, index: index, content: .groupReference( @@ -428,21 +429,22 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL } } -private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: EngineChatList.Group, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { +private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { return entries.map { entry -> ListViewUpdateItem in switch entry.entry { - case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): switch mode { case .chatList: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: presentationData, context: context, - peerGroupId: peerGroupId, + chatListLocation: location, filterData: filterData, index: index, content: .peer( messages: messages, peer: peer, + threadInfo: threadInfo, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, @@ -528,7 +530,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL if editing { nodeInteraction.togglePeerSelected(chatPeer) } else { - nodeInteraction.peerSelected(chatPeer, nil, nil) + nodeInteraction.peerSelected(chatPeer, nil, nil, nil) } } }, disabledAction: { _ in @@ -546,7 +548,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( presentationData: presentationData, context: context, - peerGroupId: peerGroupId, + chatListLocation: location, filterData: filterData, index: index, content: .groupReference( @@ -590,8 +592,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL } } -private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: EngineChatList.Group, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { - return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, filterData: filterData, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, filterData: filterData, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade) +private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { + return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade) } private final class ChatListOpaqueTransactionState { @@ -627,7 +629,7 @@ public enum ChatListNodeEmptyState: Equatable { public final class ChatListNode: ListView { private let fillPreloadItems: Bool private let context: AccountContext - private let groupId: EngineChatList.Group + private let location: ChatListControllerLocation private let mode: ChatListNodeMode private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer @@ -644,7 +646,7 @@ public final class ChatListNode: ListView { return _contentsReady.get() } - public var peerSelected: ((EnginePeer, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? + public var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? public var disabledPeerSelected: ((EnginePeer) -> Void)? public var additionalCategorySelected: ((Int) -> Void)? public var groupSelected: ((EngineChatList.Group) -> Void)? @@ -745,9 +747,9 @@ public final class ChatListNode: ListView { public var selectionLimit: Int32 = 100 public var reachedSelectionLimit: ((Int32) -> Void)? - public init(context: AccountContext, groupId: EngineChatList.Group, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool) { + public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool) { self.context = context - self.groupId = groupId + self.location = location self.chatListFilter = chatListFilter self.chatListFilterValue.set(.single(chatListFilter)) self.fillPreloadItems = fillPreloadItems @@ -776,9 +778,9 @@ public final class ChatListNode: ListView { if let strongSelf = self, let activateSearch = strongSelf.activateSearch { activateSearch() } - }, peerSelected: { [weak self] peer, _, promoInfo in + }, peerSelected: { [weak self] peer, threadId, _, promoInfo in if let strongSelf = self, let peerSelected = strongSelf.peerSelected { - peerSelected(peer, true, true, promoInfo) + peerSelected(peer, threadId, true, true, promoInfo) } }, disabledPeerSelected: { [weak self] peer in if let strongSelf = self, let disabledPeerSelected = strongSelf.disabledPeerSelected { @@ -842,7 +844,7 @@ public final class ChatListNode: ListView { } }, additionalCategorySelected: { [weak self] id in self?.additionalCategorySelected?(id) - }, messageSelected: { [weak self] peer, message, promoInfo in + }, messageSelected: { [weak self] peer, threadId, message, promoInfo in if let strongSelf = self, let peerSelected = strongSelf.peerSelected { var activateInput = false for media in message.media { @@ -855,7 +857,7 @@ public final class ChatListNode: ListView { } } } - peerSelected(peer, true, activateInput, promoInfo) + peerSelected(peer, threadId, true, activateInput, promoInfo) } }, groupSelected: { [weak self] groupId in if let strongSelf = self, let groupSelected = strongSelf.groupSelected { @@ -876,7 +878,14 @@ public final class ChatListNode: ListView { } }, setItemPinned: { [weak self] itemId, _ in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in + |> deliverOnMainQueue).start(next: { peer in + guard let strongSelf = self else { + return + } + guard case let .chatList(groupId) = strongSelf.location else { + return + } + let isPremium = peer?.isPremium ?? false let location: TogglePeerChatPinnedLocation if let chatListFilter = chatListFilter { @@ -979,10 +988,10 @@ public final class ChatListNode: ListView { let chatListViewUpdate = self.chatListLocation.get() |> distinctUntilChanged - |> mapToSignal { location -> Signal<(ChatListNodeViewUpdate, ChatListFilter?), NoError> in - return chatListViewForLocation(groupId: groupId._asGroup(), location: location, account: context.account) + |> mapToSignal { listLocation -> Signal<(ChatListNodeViewUpdate, ChatListFilter?), NoError> in + return chatListViewForLocation(chatListLocation: location, location: listLocation, account: context.account) |> map { update in - return (update, location.filter) + return (update, listLocation.filter) } } @@ -1010,7 +1019,7 @@ public final class ChatListNode: ListView { |> distinctUntilChanged let displayArchiveIntro: Signal - if case .archive = groupId { + if case .chatList(.archive) = location { displayArchiveIntro = context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.archiveIntroDismissedKey()) |> map { entry -> Bool in if let value = entry.value?.get(ApplicationSpecificVariantNotice.self) { @@ -1041,123 +1050,123 @@ public final class ChatListNode: ListView { let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault) - let (rawEntries, isLoading) = chatListNodeEntriesForView(EngineChatList(update.view), state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode) + let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode) let entries = rawEntries.filter { entry in switch entry { - case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _): switch mode { - case .chatList: - return true - case let .peers(filter, _, _, _): - guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false } - guard !filter.contains(.excludeSavedMessages) || !peer.peerId.isReplies else { return false } - guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false } - guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false } + case .chatList: + return true + case let .peers(filter, _, _, _): + guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false } + guard !filter.contains(.excludeSavedMessages) || !peer.peerId.isReplies else { return false } + guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false } + guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false } - if let peer = peer.peer { - switch peer { - case let .user(user): - if user.botInfo != nil { - if filter.contains(.excludeBots) { - return false - } - } else { - if filter.contains(.excludeUsers) { - return false - } - } - case .legacyGroup: - if filter.contains(.excludeGroups) { - return false - } - case let .channel(channel): - switch channel.info { - case .broadcast: - if filter.contains(.excludeChannels) { - return false - } - case .group: - if filter.contains(.excludeGroups) { - return false - } - } - default: - break + if let peer = peer.peer { + switch peer { + case let .user(user): + if user.botInfo != nil { + if filter.contains(.excludeBots) { + return false + } + } else { + if filter.contains(.excludeUsers) { + return false + } + } + case .legacyGroup: + if filter.contains(.excludeGroups) { + return false + } + case let .channel(channel): + switch channel.info { + case .broadcast: + if filter.contains(.excludeChannels) { + return false + } + case .group: + if filter.contains(.excludeGroups) { + return false + } + } + default: + break + } + } + + if filter.contains(.onlyGroupsAndChannels) { + if case .channel = peer.chatMainPeer { + } else if case .legacyGroup = peer.chatMainPeer { + } else { + return false + } + } else { + if filter.contains(.onlyGroups) { + var isGroup: Bool = false + if case let .channel(peer) = peer.chatMainPeer, case .group = peer.info { + isGroup = true + } else if peer.peerId.namespace == Namespaces.Peer.CloudGroup { + isGroup = true + } + if !isGroup { + return false } } - - if filter.contains(.onlyGroupsAndChannels) { - if case .channel = peer.chatMainPeer { - } else if case .legacyGroup = peer.chatMainPeer { + + if filter.contains(.onlyChannels) { + if case let .channel(peer) = peer.chatMainPeer, case .broadcast = peer.info { + } else { + return false + } + } + } + + if filter.contains(.excludeChannels) { + if case let .channel(peer) = peer.chatMainPeer, case .broadcast = peer.info { + } + } + + if filter.contains(.onlyWriteable) && filter.contains(.excludeDisabled) { + if let peer = peer.peers[peer.peerId] { + if !canSendMessagesToPeer(peer._asPeer()) { + return false + } + } else { + return false + } + } + + if filter.contains(.onlyManageable) && filter.contains(.excludeDisabled) { + if let peer = peer.peers[peer.peerId] { + var canManage = false + if case let .legacyGroup(peer) = peer { + switch peer.role { + case .creator, .admin: + canManage = true + default: + break + } + } + + if canManage { + } else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) { } else { return false } } else { - if filter.contains(.onlyGroups) { - var isGroup: Bool = false - if case let .channel(peer) = peer.chatMainPeer, case .group = peer.info { - isGroup = true - } else if peer.peerId.namespace == Namespaces.Peer.CloudGroup { - isGroup = true - } - if !isGroup { - return false - } - } - - if filter.contains(.onlyChannels) { - if case let .channel(peer) = peer.chatMainPeer, case .broadcast = peer.info { - } else { - return false - } - } + return false } - - if filter.contains(.excludeChannels) { - if case let .channel(peer) = peer.chatMainPeer, case .broadcast = peer.info { - } - } - - if filter.contains(.onlyWriteable) && filter.contains(.excludeDisabled) { - if let peer = peer.peers[peer.peerId] { - if !canSendMessagesToPeer(peer._asPeer()) { - return false - } - } else { - return false - } - } - - if filter.contains(.onlyManageable) && filter.contains(.excludeDisabled) { - if let peer = peer.peers[peer.peerId] { - var canManage = false - if case let .legacyGroup(peer) = peer { - switch peer.role { - case .creator, .admin: - canManage = true - default: - break - } - } - - if canManage { - } else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) { - } else { - return false - } - } else { - return false - } - } - - return true } - default: + return true + } + default: + return true } } - let processedView = ChatListNodeView(originalView: update.view, filteredEntries: entries, isLoading: isLoading, filter: filter) + let processedView = ChatListNodeView(originalList: update.list, filteredEntries: entries, isLoading: isLoading, filter: filter) let previousView = previousView.swap(processedView) let previousState = previousState.swap(state) @@ -1185,7 +1194,7 @@ public final class ChatListNode: ListView { prepareOnMainQueue = true } } else { - if previousView?.originalView === update.view { + if previousView?.originalList === update.list { reason = .interactiveChanges updatedScrollPosition = nil } else { @@ -1216,12 +1225,14 @@ public final class ChatListNode: ListView { var didIncludeHiddenByDefaultArchive = false if let previous = previousView { for entry in previous.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { - if index.pinningIndex != nil { - previousPinnedChats.append(index.messageIndex.id.peerId) - } - if index.messageIndex.id.peerId == removingPeerId { - didIncludeRemovingPeerId = true + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .chatList(chatListIndex) = index { + if chatListIndex.pinningIndex != nil { + previousPinnedChats.append(chatListIndex.messageIndex.id.peerId) + } + if chatListIndex.messageIndex.id.peerId == removingPeerId { + didIncludeRemovingPeerId = true + } } } else if case let .GroupReferenceEntry(_, _, _, _, _, _, _, _, hiddenByDefault) = entry { didIncludeHiddenByDefaultArchive = hiddenByDefault @@ -1232,11 +1243,11 @@ public final class ChatListNode: ListView { var doesIncludeArchive = false var doesIncludeHiddenByDefaultArchive = false for entry in processedView.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { - if index.pinningIndex != nil { + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .chatList(index) = index, index.pinningIndex != nil { updatedPinnedChats.append(index.messageIndex.id.peerId) } - if index.messageIndex.id.peerId == removingPeerId { + if case let .chatList(index) = index, index.messageIndex.id.peerId == removingPeerId { doesIncludeRemovingPeerId = true } } else if case let .GroupReferenceEntry(_, _, _, _, _, _, _, _, hiddenByDefault) = entry { @@ -1287,7 +1298,7 @@ public final class ChatListNode: ListView { } return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode) - |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, peerGroupId: groupId, filterData: filterData, mode: mode, transition: $0) }) + |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue) } @@ -1300,13 +1311,13 @@ public final class ChatListNode: ListView { self.displayedItemRangeChanged = { [weak self] range, transactionOpaqueState in if let strongSelf = self, let chatListView = (transactionOpaqueState as? ChatListOpaqueTransactionState)?.chatListView { - let originalView = chatListView.originalView + let originalList = chatListView.originalList if let range = range.loadedRange { var location: ChatListNodeLocation? - if range.firstIndex < 5, let laterIndex = originalView.laterIndex { - location = .navigation(index: laterIndex, filter: strongSelf.chatListFilter) - } else if range.firstIndex >= 5, range.lastIndex >= originalView.entries.count - 5, let earlierIndex = originalView.earlierIndex { - location = .navigation(index: earlierIndex, filter: strongSelf.chatListFilter) + if range.firstIndex < 5, let lastItem = originalList.items.last, originalList.hasLater { + location = .navigation(index: lastItem.index, filter: strongSelf.chatListFilter) + } else if range.firstIndex >= 5, range.lastIndex >= originalList.items.count - 5, originalList.hasEarlier, let firstItem = originalList.items.first { + location = .navigation(index: firstItem.index, filter: strongSelf.chatListFilter) } if let location = location, location != strongSelf.currentLocation { @@ -1485,6 +1496,10 @@ public final class ChatListNode: ListView { self.reorderItem = { [weak self] fromIndex, toIndex, transactionOpaqueState -> Signal in if let strongSelf = self, let filteredEntries = (transactionOpaqueState as? ChatListOpaqueTransactionState)?.chatListView.filteredEntries { + guard case let .chatList(groupId) = strongSelf.location else { + return .single(false) + } + if fromIndex >= 0 && fromIndex < filteredEntries.count && toIndex >= 0 && toIndex < filteredEntries.count { let fromEntry = filteredEntries[filteredEntries.count - 1 - fromIndex] let toEntry = filteredEntries[filteredEntries.count - 1 - toIndex] @@ -1492,17 +1507,19 @@ public final class ChatListNode: ListView { var referenceId: EngineChatList.PinnedItem.Id? var beforeAll = false switch toEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _): if promoInfo != nil { beforeAll = true } else { - referenceId = .peer(index.messageIndex.id.peerId) + if case let .chatList(chatListIndex) = index { + referenceId = .peer(chatListIndex.messageIndex.id.peerId) + } } default: break } - if case let .index(index) = fromEntry.sortIndex, let _ = index.pinningIndex { + if case let .index(index) = fromEntry.sortIndex, case let .chatList(chatListIndex) = index, let _ = chatListIndex.pinningIndex { let location: TogglePeerChatPinnedLocation if let chatListFilter = chatListFilter { location = .filter(chatListFilter.id) @@ -1517,8 +1534,10 @@ public final class ChatListNode: ListView { var itemId: EngineChatList.PinnedItem.Id? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - itemId = .peer(index.messageIndex.id.peerId) + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + if case let .chatList(index) = index { + itemId = .peer(index.messageIndex.id.peerId) + } default: break } @@ -1763,16 +1782,18 @@ public final class ChatListNode: ListView { if strongSelf.fillPreloadItems { let filteredEntries = transition.chatListView.filteredEntries var preloadItems: [ChatHistoryPreloadItem] = [] - if transition.chatListView.originalView.laterIndex == nil { + if !transition.chatListView.originalList.hasLater { for entry in filteredEntries.reversed() { switch entry { - case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, promoInfo, _, _): + case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _): if promoInfo == nil { var hasUnread = false if let combinedReadState = combinedReadState { hasUnread = combinedReadState.count > 0 } - preloadItems.append(ChatHistoryPreloadItem(index: index, isMuted: isMuted, hasUnread: hasUnread)) + if case let .chatList(index) = index { + preloadItems.append(ChatHistoryPreloadItem(index: index, isMuted: isMuted, hasUnread: hasUnread)) + } } default: break @@ -1789,7 +1810,7 @@ public final class ChatListNode: ListView { if case .chatList = strongSelf.mode { let entryCount = transition.chatListView.filteredEntries.count if entryCount >= 1 { - if case let .index(index) = transition.chatListView.filteredEntries[entryCount - 1].sortIndex, index.pinningIndex != nil { + if case let .index(index) = transition.chatListView.filteredEntries[entryCount - 1].sortIndex, case let .chatList(chatListIndex) = index, chatListIndex.pinningIndex != nil { pinnedOverscroll = true } } @@ -1887,10 +1908,10 @@ public final class ChatListNode: ListView { for item in transition.insertItems { if let item = item.item as? ChatListItem { switch item.content { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _): - insertedPeerIds.append(peer.peerId) - case .groupReference: - break + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _): + insertedPeerIds.append(peer.peerId) + case .groupReference: + break } } } @@ -2002,16 +2023,15 @@ public final class ChatListNode: ListView { } public func scrollToPosition(_ position: ChatListNodeScrollPosition) { - if let view = self.chatListView?.originalView { - if view.laterIndex == nil { + if let list = self.chatListView?.originalList { + if !list.hasLater { self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else { - let location: ChatListNodeLocation = .scroll(index: .absoluteUpperBound, sourceIndex: .absoluteLowerBound, scrollPosition: .top(0.0), animated: true, filter: self.chatListFilter) + let location: ChatListNodeLocation = .scroll(index: .chatList(.absoluteUpperBound), sourceIndex: .chatList(.absoluteLowerBound), scrollPosition: .top(0.0), animated: true, filter: self.chatListFilter) self.setChatListLocation(location) } } else { - let location: ChatListNodeLocation = .scroll(index: .absoluteUpperBound, sourceIndex: .absoluteLowerBound - , scrollPosition: .top(0.0), animated: true, filter: self.chatListFilter) + let location: ChatListNodeLocation = .scroll(index: .chatList(.absoluteUpperBound), sourceIndex: .chatList(.absoluteLowerBound), scrollPosition: .top(0.0), animated: true, filter: self.chatListFilter) self.setChatListLocation(location) } } @@ -2022,7 +2042,10 @@ public final class ChatListNode: ListView { } private func relativeUnreadChatListIndex(position: EngineChatList.RelativePosition) -> Signal { - let groupId = self.groupId + guard case let .chatList(groupId) = self.location else { + return .single(nil) + } + let engine = self.context.engine return self.context.sharedContext.accountManager.transaction { transaction -> Signal in var filter = true @@ -2061,7 +2084,7 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _): if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peer.peerId) { current = (index, peer.peer!, entryCount - i - 1) break outer @@ -2086,11 +2109,11 @@ public final class ChatListNode: ListView { let engine = self.context.engine let _ = (relativeUnreadChatListIndex(position: position) |> mapToSignal { index -> Signal<(EngineChatList.Item.Index, EnginePeer)?, NoError> in - if let index = index { + if case let .chatList(index) = index { return engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: index.messageIndex.id.peerId)) |> map { peer -> (EngineChatList.Item.Index, EnginePeer)? in return peer.flatMap { peer -> (EngineChatList.Item.Index, EnginePeer)? in - (index, peer) + (.chatList(index), peer) } } } else { @@ -2101,17 +2124,17 @@ public final class ChatListNode: ListView { guard let strongSelf = self, let (index, peer) = indexAndPeer else { return } - let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: strongSelf.currentlyVisibleLatestChatListIndex() ?? .absoluteUpperBound, scrollPosition: .center(.top), animated: true, filter: strongSelf.chatListFilter) + let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: strongSelf.currentlyVisibleLatestChatListIndex() ?? .chatList(.absoluteLowerBound), scrollPosition: .center(.top), animated: true, filter: strongSelf.chatListFilter) strongSelf.setChatListLocation(location) - strongSelf.peerSelected?(peer, false, false, nil) + strongSelf.peerSelected?(peer, nil, false, false, nil) }) case .previous(unread: false), .next(unread: false): var target: (EngineChatList.Item.Index, EnginePeer)? = nil if let current = current, entryCount > 1 { - if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { + if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { next = (index, peer.peer!) } - if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { + if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { previous = (index, peer.peer!) } if case .previous = option { @@ -2120,14 +2143,14 @@ public final class ChatListNode: ListView { target = next } } else if entryCount > 0 { - if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { + if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { target = (index, peer.peer!) } } if let target = target { - let location: ChatListNodeLocation = .scroll(index: target.0, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true, filter: self.chatListFilter) + let location: ChatListNodeLocation = .scroll(index: target.0, sourceIndex: .chatList(.absoluteLowerBound), scrollPosition: .center(.top), animated: true, filter: self.chatListFilter) self.setChatListLocation(location) - self.peerSelected?(target.1, false, false, nil) + self.peerSelected?(target.1, nil, false, false, nil) } case let .peerId(peerId): let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) @@ -2135,7 +2158,7 @@ public final class ChatListNode: ListView { guard let strongSelf = self, let peer = peer else { return } - strongSelf.peerSelected?(peer, false, false, nil) + strongSelf.peerSelected?(peer, nil, false, false, nil) }) case let .index(index): guard index < 10 else { @@ -2147,14 +2170,18 @@ public final class ChatListNode: ListView { guard let self = self else { return } - let _ = (chatListViewForLocation(groupId: self.groupId._asGroup(), location: .initial(count: 10, filter: filter), account: self.context.account) + guard case let .chatList(groupId) = self.location else { + return + } + let _ = (chatListViewForLocation(chatListLocation: .chatList(groupId: groupId), location: .initial(count: 10, filter: filter), account: self.context.account) |> take(1) |> deliverOnMainQueue).start(next: { update in - let entries = update.view.entries - if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _, _) = entries[9 - index - 1] { - let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true, filter: filter) + let items = update.list.items + if items.count > index { + let item = items[9 - index - 1] + let location: ChatListNodeLocation = .scroll(index: item.index, sourceIndex: .chatList(.absoluteLowerBound), scrollPosition: .center(.top), animated: true, filter: filter) self.setChatListLocation(location) - self.peerSelected?(EnginePeer(renderedPeer.peer!), false, false, nil) + self.peerSelected?(EnginePeer(item.renderedPeer.peer!._asPeer()), nil, false, false, nil) } }) }) @@ -2194,7 +2221,7 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return index default: break @@ -2210,7 +2237,7 @@ public final class ChatListNode: ListView { if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) { if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { switch item.content { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _): resultPeer = peer.peer default: break diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index dceb3994b3..ea7d7d65e6 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -10,6 +10,7 @@ enum ChatListNodeEntryId: Hashable { case Header case Hole(Int64) case PeerId(Int64) + case ThreadId(Int64) case GroupId(EngineChatList.Group) case ArchiveIntro case additionalCategory(Int) @@ -46,7 +47,7 @@ public enum ChatListNodeEntryPromoInfo: Equatable { enum ChatListNodeEntry: Comparable, Identifiable { case HeaderEntry - case PeerEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, messages: [EngineMessage], readState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, draftState: ChatListItemContent.DraftState?, peer: EngineRenderedPeer, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool) + case PeerEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, messages: [EngineMessage], readState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, draftState: ChatListItemContent.DraftState?, peer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThreads.Info?, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool) case HoleEntry(EngineMessage.Index, theme: PresentationTheme) case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool) case ArchiveIntro(presentationData: ChatListPresentationData) @@ -55,15 +56,15 @@ enum ChatListNodeEntry: Comparable, Identifiable { var sortIndex: ChatListNodeEntrySortIndex { switch self { case .HeaderEntry: - return .index(EngineChatList.Item.Index.absoluteUpperBound) - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + return .index(.chatList(.absoluteUpperBound)) + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return .index(index) case let .HoleEntry(holeIndex, _): - return .index(EngineChatList.Item.Index(pinningIndex: nil, messageIndex: holeIndex)) + return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex))) case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _): return .index(index) case .ArchiveIntro: - return .index(EngineChatList.Item.Index.absoluteUpperBound.successor) + return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor)) case let .AdditionalCategory(index, _, _, _, _, _, _): return .additionalCategory(index) } @@ -73,8 +74,13 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .Header - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - return .PeerId(index.messageIndex.id.peerId.toInt64()) + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + switch index { + case let .chatList(index): + return .PeerId(index.messageIndex.id.peerId.toInt64()) + case let .forum(_, threadId, _, _): + return .ThreadId(threadId) + } case let .HoleEntry(holeIndex, _): return .Hole(Int64(holeIndex.id.id)) case let .GroupReferenceEntry(_, _, groupId, _, _, _, _, _, _): @@ -98,9 +104,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } - case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact): + case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact): switch rhs { - case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact): + case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact): if lhsIndex != rhsIndex { return false } @@ -162,6 +168,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhsPeer != rhsPeer { return false } + if lhsThreadInfo != rhsThreadInfo { + return false + } if lhsHasUnseenMentions != rhsHasUnseenMentions { return false } @@ -277,8 +286,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { } private func offsetPinnedIndex(_ index: EngineChatList.Item.Index, offset: UInt16) -> EngineChatList.Item.Index { - if let pinningIndex = index.pinningIndex { - return EngineChatList.Item.Index(pinningIndex: pinningIndex + offset, messageIndex: index.messageIndex) + if case let .chatList(index) = index, let pinningIndex = index.pinningIndex { + return .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex + offset, messageIndex: index.messageIndex)) } else { return index } @@ -310,16 +319,20 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState pinnedIndexOffset += UInt16(filteredAdditionalItemEntries.count) } loop: for entry in view.items { - //case let .MessageEntry(index, messages, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact): - if let savedMessagesPeer = savedMessagesPeer, savedMessagesPeer.id == entry.index.messageIndex.id.peerId || foundPeerIds.contains(entry.index.messageIndex.id.peerId) { + var peerId: EnginePeer.Id? + if case let .chatList(index) = entry.index { + peerId = index.messageIndex.id.peerId + } + + if let savedMessagesPeer = savedMessagesPeer, let peerId = peerId, savedMessagesPeer.id == peerId || foundPeerIds.contains(peerId) { continue loop } - if state.pendingRemovalPeerIds.contains(entry.index.messageIndex.id.peerId) { + if let peerId = peerId, state.pendingRemovalPeerIds.contains(peerId) { continue loop } var updatedMessages = entry.messages var updatedCombinedReadState = entry.readCounters - if state.pendingClearHistoryPeerIds.contains(entry.index.messageIndex.id.peerId) { + if let peerId = peerId, state.pendingClearHistoryPeerIds.contains(peerId) { updatedMessages = [] updatedCombinedReadState = nil } @@ -328,8 +341,21 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState if let draft = entry.draft { draftState = ChatListItemContent.DraftState(draft: draft) } + + var hasActiveRevealControls = false + if let peerId { + hasActiveRevealControls = peerId == state.peerIdWithRevealedOptions + } + var isSelected = false + if let peerId { + isSelected = state.selectedPeerIds.contains(peerId) + } + var inputActivities: [(EnginePeer, PeerInputActivity)]? + if let peerId { + inputActivities = state.peerInputActivities?.activities[peerId] + } - result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: entry.index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(entry.index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[entry.index.messageIndex.id.peerId], promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact)) + result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact)) } if !view.hasLater { var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) @@ -345,13 +371,14 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState let messageIndex = EngineMessage.Index(id: EngineMessage.Id(peerId: peer.0.id, namespace: 0, id: 0), timestamp: 1) result.append(.PeerEntry( - index: EngineChatList.Item.Index(pinningIndex: foundPinningIndex, messageIndex: messageIndex), + index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: foundPinningIndex, messageIndex: messageIndex)), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: peer.0.id, peers: peers, associatedMedia: [:]), + threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, @@ -369,10 +396,14 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState } } - result.append(.PeerEntry(index: EngineChatList.Item.Index.absoluteUpperBound.predecessor, presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false)) + result.append(.PeerEntry(index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false)) } else { if !filteredAdditionalItemEntries.isEmpty { for item in filteredAdditionalItemEntries.reversed() { + guard case let .chatList(index) = item.item.index else { + continue + } + let promoInfo: ChatListNodeEntryPromoInfo switch item.promoInfo.content { case .proxy: @@ -381,21 +412,26 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo = .psa(type: type, message: message) } let draftState = item.item.draft.flatMap(ChatListItemContent.DraftState.init) + + let peerId = index.messageIndex.id.peerId + let isSelected = state.selectedPeerIds.contains(peerId) + result.append(.PeerEntry( - index: EngineChatList.Item.Index(pinningIndex: pinningIndex, messageIndex: item.item.index.messageIndex), + index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: index.messageIndex)), presentationData: state.presentationData, messages: item.item.messages, readState: item.item.readCounters, isRemovedFromTotalUnreadCount: item.item.isMuted, draftState: draftState, peer: item.item.renderedPeer, + threadInfo: item.item.threadInfo, presence: item.item.presence, hasUnseenMentions: item.item.hasUnseenMentions, hasUnseenReactions: item.item.hasUnseenReactions, editing: state.editing, - hasActiveRevealControls: item.item.index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, - selected: state.selectedPeerIds.contains(item.item.index.messageIndex.id.peerId), - inputActivities: state.peerInputActivities?.activities[item.item.index.messageIndex.id.peerId], + hasActiveRevealControls: peerId == state.peerIdWithRevealedOptions, + selected: isSelected, + inputActivities: state.peerInputActivities?.activities[peerId], promoInfo: promoInfo, hasFailedMessages: item.item.hasFailed, isContact: item.item.isContact @@ -411,7 +447,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState for groupReference in view.groupItems { let messageIndex = EngineMessage.Index(id: EngineMessage.Id(peerId: EnginePeer.Id(0), namespace: 0, id: 0), timestamp: 1) result.append(.GroupReferenceEntry( - index: EngineChatList.Item.Index(pinningIndex: pinningIndex, messageIndex: messageIndex), + index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: messageIndex)), presentationData: state.presentationData, groupId: groupReference.id, peers: groupReference.items, diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index bb89d776e9..9ac6d566d1 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -4,11 +4,12 @@ import TelegramCore import SwiftSignalKit import Display import TelegramUIPreferences +import AccountContext enum ChatListNodeLocation: Equatable { case initial(count: Int, filter: ChatListFilter?) - case navigation(index: ChatListIndex, filter: ChatListFilter?) - case scroll(index: ChatListIndex, sourceIndex: ChatListIndex, scrollPosition: ListViewScrollPosition, animated: Bool, filter: ChatListFilter?) + case navigation(index: EngineChatList.Item.Index, filter: ChatListFilter?) + case scroll(index: EngineChatList.Item.Index, sourceIndex: EngineChatList.Item.Index, scrollPosition: ListViewScrollPosition, animated: Bool, filter: ChatListFilter?) var filter: ChatListFilter? { switch self { @@ -23,7 +24,7 @@ enum ChatListNodeLocation: Equatable { } struct ChatListNodeViewUpdate { - let view: ChatListView + let list: EngineChatList let type: ViewUpdateType let scrollPosition: ChatListNodeViewScrollPosition? } @@ -109,25 +110,30 @@ public func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilte }) } -func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal { - let filterPredicate: ChatListFilterPredicate? - if let filter = location.filter, case let .filter(_, _, _, data) = filter { - filterPredicate = chatListFilterPredicate(filter: data) - } else { - filterPredicate = nil - } - - switch location { +func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account) -> Signal { + switch chatListLocation { + case let .chatList(groupId): + let filterPredicate: ChatListFilterPredicate? + if let filter = location.filter, case let .filter(_, _, _, data) = filter { + filterPredicate = chatListFilterPredicate(filter: data) + } else { + filterPredicate = nil + } + + switch location { case let .initial(count, _): let signal: Signal<(ChatListView, ViewUpdateType), NoError> - signal = account.viewTracker.tailChatListView(groupId: groupId, filterPredicate: filterPredicate, count: count) + signal = account.viewTracker.tailChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, count: count) return signal |> map { view, updateType -> ChatListNodeViewUpdate in - return ChatListNodeViewUpdate(view: view, type: updateType, scrollPosition: nil) + return ChatListNodeViewUpdate(list: EngineChatList(view), type: updateType, scrollPosition: nil) } case let .navigation(index, _): + guard case let .chatList(index) = index else { + return .never() + } var first = true - return account.viewTracker.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: 80) + return account.viewTracker.aroundChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, index: index, count: 80) |> map { view, updateType -> ChatListNodeViewUpdate in let genericType: ViewUpdateType if first { @@ -136,13 +142,17 @@ func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocatio } else { genericType = updateType } - return ChatListNodeViewUpdate(view: view, type: genericType, scrollPosition: nil) + return ChatListNodeViewUpdate(list: EngineChatList(view), type: genericType, scrollPosition: nil) } case let .scroll(index, sourceIndex, scrollPosition, animated, _): - let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up + guard case let .chatList(index) = index else { + return .never() + } + + let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > .chatList(index) ? .Down : .Up let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) var first = true - return account.viewTracker.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: 80) + return account.viewTracker.aroundChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, index: index, count: 80) |> map { view, updateType -> ChatListNodeViewUpdate in let genericType: ViewUpdateType let scrollPosition: ChatListNodeViewScrollPosition? = first ? chatScrollPosition : nil @@ -152,7 +162,60 @@ func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocatio } else { genericType = updateType } - return ChatListNodeViewUpdate(view: view, type: genericType, scrollPosition: scrollPosition) + return ChatListNodeViewUpdate(list: EngineChatList(view), type: genericType, scrollPosition: scrollPosition) } + } + case let .forum(peerId): + let viewKey: PostboxViewKey = .messageHistoryThreadIndex(id: peerId) + var isFirst = false + return account.postbox.combinedView(keys: [viewKey]) + |> map { views -> ChatListNodeViewUpdate in + guard let view = views.views[viewKey] as? MessageHistoryThreadIndexView else { + preconditionFailure() + } + + var items: [EngineChatList.Item] = [] + for item in view.items { + guard let peer = view.peer else { + continue + } + guard let info = item.info.get(EngineMessageHistoryThreads.Info.self) else { + continue + } + items.append(EngineChatList.Item( + id: .forum(item.id), + index: .forum(timestamp: item.index.timestamp, threadId: item.id, namespace: item.index.id.namespace, id: item.index.id.id), + messages: item.topMessage.flatMap { [EngineMessage($0)] } ?? [], + readCounters: nil, + isMuted: false, + draft: nil, + threadInfo: info, + renderedPeer: EngineRenderedPeer(peer: EnginePeer(peer)), + presence: nil, + hasUnseenMentions: false, + hasUnseenReactions: false, + hasFailed: false, + isContact: false + )) + } + + let list = EngineChatList( + items: items.reversed(), + groupItems: [], + additionalItems: [], + hasEarlier: false, + hasLater: false, + isLoading: false + ) + + let type: ViewUpdateType + if isFirst { + type = .Initial + } else { + type = .Generic + } + isFirst = false + return ChatListNodeViewUpdate(list: list, type: type, scrollPosition: nil) + } } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift b/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift index 4b47e136c8..43111a1372 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListTypingNode.swift @@ -19,7 +19,7 @@ final class ChatListInputActivitiesNode: ASDisplayNode { self.addSubnode(self.activityNode) } - func asyncLayout() -> (CGSize, ChatListPresentationData, UIColor, EnginePeer.Id, [(EnginePeer, PeerInputActivity)]) -> (CGSize, () -> Void) { + func asyncLayout() -> (CGSize, ChatListPresentationData, UIColor, EnginePeer.Id?, [(EnginePeer, PeerInputActivity)]) -> (CGSize, () -> Void) { return { [weak self] boundingSize, presentationData, color, peerId, activities in let strings = presentationData.strings diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index cb6d2f4be7..97be3219c5 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -8,7 +8,7 @@ import SearchUI import TelegramUIPreferences struct ChatListNodeView { - let originalView: ChatListView + let originalList: EngineChatList let filteredEntries: [ChatListNodeEntry] let isLoading: Bool let filter: ChatListFilter? @@ -52,7 +52,7 @@ enum ChatListNodeViewScrollPosition { } func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toView: ChatListNodeView, reason: ChatListNodeViewTransitionReason, previewing: Bool, disableAnimations: Bool, account: Account, scrollPosition: ChatListNodeViewScrollPosition?, searchMode: Bool) -> Signal { - return Signal { subscriber in + return Signal { subscriber in let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries) var adjustedDeleteIndices: [ListViewDeleteItem] = [] @@ -74,44 +74,40 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV var scrollToItem: ListViewScrollToItem? switch reason { - case .initial: - let _ = options.insert(.LowLatency) - let _ = options.insert(.Synchronous) - case .interactiveChanges: - for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) { - let adjustedIndex = updatedCount - 1 - index - if adjustedIndex == maxAnimatedInsertionIndex + 1 { - maxAnimatedInsertionIndex += 1 - } + case .initial: + let _ = options.insert(.LowLatency) + let _ = options.insert(.Synchronous) + case .interactiveChanges: + for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) { + let adjustedIndex = updatedCount - 1 - index + if adjustedIndex == maxAnimatedInsertionIndex + 1 { + maxAnimatedInsertionIndex += 1 } + } - var minTimestamp: Int32? - var maxTimestamp: Int32? - for (_, item, _) in indicesAndItems { - if case .PeerEntry = item, case let .index(index) = item.sortIndex, index.pinningIndex == nil { - let timestamp = index.messageIndex.timestamp - - if minTimestamp == nil || timestamp < minTimestamp! { - minTimestamp = timestamp - } - if maxTimestamp == nil || timestamp > maxTimestamp! { - maxTimestamp = timestamp - } + var minTimestamp: Int32? + var maxTimestamp: Int32? + for (_, item, _) in indicesAndItems { + if case .PeerEntry = item, case let .index(index) = item.sortIndex, case let .chatList(chatListIndex) = index, chatListIndex.pinningIndex == nil { + let timestamp = chatListIndex.messageIndex.timestamp + + if minTimestamp == nil || timestamp < minTimestamp! { + minTimestamp = timestamp + } + if maxTimestamp == nil || timestamp > maxTimestamp! { + maxTimestamp = timestamp } } + } - if false, let minTimestamp = minTimestamp, let maxTimestamp = maxTimestamp, abs(maxTimestamp - minTimestamp) > 60 * 60 { - let _ = options.insert(.AnimateCrossfade) - } else { - let _ = options.insert(.AnimateAlpha) - if !disableAnimations { - let _ = options.insert(.AnimateInsertion) - } - } - case .reload: - break - case .holeChanges: - break + let _ = options.insert(.AnimateAlpha) + if !disableAnimations { + let _ = options.insert(.AnimateInsertion) + } + case .reload: + break + case .holeChanges: + break } for (index, entry, previousIndex) in indicesAndItems { @@ -142,31 +138,33 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV if let scrollPosition = scrollPosition { switch scrollPosition { - case let .index(scrollIndex, position, directionHint, animated): - var index = toView.filteredEntries.count - 1 - for entry in toView.filteredEntries { - if entry.sortIndex >= .index(scrollIndex) { + case let .index(scrollIndex, position, directionHint, animated): + var index = toView.filteredEntries.count - 1 + for entry in toView.filteredEntries { + if entry.sortIndex >= .index(.chatList(scrollIndex)) { + scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) + break + } + index -= 1 + } + + if scrollToItem == nil { + var index = 0 + for entry in toView.filteredEntries.reversed() { + if entry.sortIndex < .index(.chatList(scrollIndex)) { scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) break } - index -= 1 - } - - if scrollToItem == nil { - var index = 0 - for entry in toView.filteredEntries.reversed() { - if entry.sortIndex < .index(scrollIndex) { - scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) - break - } - index += 1 - } + index += 1 } + } } } - var fromEmptyView = false - var animateCrossfade = false + var fromEmptyView: Bool + fromEmptyView = false + var animateCrossfade: Bool + animateCrossfade = false if let fromView = fromView { var wasSingleHeader = false if fromView.filteredEntries.count == 1, case .HeaderEntry = fromView.filteredEntries[0] { diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index b56d2ee3dc..5d51a5cbd6 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -57,12 +57,12 @@ public final class HashtagSearchController: TelegramBaseController { return result.messages.map({ .message(EngineMessage($0), EngineRenderedPeer(message: EngineMessage($0)), result.readStates[$0.id.peerId].flatMap(EnginePeerReadCounters.init), chatListPresentationData, result.totalCount, nil, false, .index($0.index), nil, .generic, false) }) } let interaction = ChatListNodeInteraction(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: { - }, peerSelected: { _, _, _ in + }, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { [weak self] peer, message, _ in + }, messageSelected: { [weak self] peer, _, message, _ in if let strongSelf = self { strongSelf.openMessageFromSearchDisposable.set((strongSelf.context.engine.peers.ensurePeerIsLocallyAvailable(peer: peer) |> deliverOnMainQueue).start(next: { actualPeerId in if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { diff --git a/submodules/OpenSSLEncryptionProvider/PublicHeaders/OpenSSLEncryptionProvider/OpenSSLEncryptionProvider.h b/submodules/OpenSSLEncryptionProvider/PublicHeaders/OpenSSLEncryptionProvider/OpenSSLEncryptionProvider.h index 7fc9e1f4f0..3dc1f9d144 100644 --- a/submodules/OpenSSLEncryptionProvider/PublicHeaders/OpenSSLEncryptionProvider/OpenSSLEncryptionProvider.h +++ b/submodules/OpenSSLEncryptionProvider/PublicHeaders/OpenSSLEncryptionProvider/OpenSSLEncryptionProvider.h @@ -6,4 +6,6 @@ NS_ASSUME_NONNULL_BEGIN @end +void debug_linker_fail_test(void); + NS_ASSUME_NONNULL_END diff --git a/submodules/Postbox/Sources/Message.swift b/submodules/Postbox/Sources/Message.swift index 5a152b6a1c..20e1e20340 100644 --- a/submodules/Postbox/Sources/Message.swift +++ b/submodules/Postbox/Sources/Message.swift @@ -473,6 +473,10 @@ public struct MessageFlags: OptionSet { rawValue |= MessageFlags.CopyProtected.rawValue } + if flags.contains(StoreMessageFlags.IsForumTopic) { + rawValue |= MessageFlags.IsForumTopic.rawValue + } + self.rawValue = rawValue } @@ -485,6 +489,7 @@ public struct MessageFlags: OptionSet { public static let WasScheduled = MessageFlags(rawValue: 128) public static let CountedAsIncoming = MessageFlags(rawValue: 256) public static let CopyProtected = MessageFlags(rawValue: 512) + public static let IsForumTopic = MessageFlags(rawValue: 1024) public static let IsIncomingMask = MessageFlags([.Incoming, .CountedAsIncoming]) } @@ -745,6 +750,10 @@ public struct StoreMessageFlags: OptionSet { rawValue |= StoreMessageFlags.CopyProtected.rawValue } + if flags.contains(.IsForumTopic) { + rawValue |= StoreMessageFlags.IsForumTopic.rawValue + } + self.rawValue = rawValue } @@ -757,6 +766,7 @@ public struct StoreMessageFlags: OptionSet { public static let WasScheduled = StoreMessageFlags(rawValue: 128) public static let CountedAsIncoming = StoreMessageFlags(rawValue: 256) public static let CopyProtected = StoreMessageFlags(rawValue: 512) + public static let IsForumTopic = StoreMessageFlags(rawValue: 1024) public static let IsIncomingMask = StoreMessageFlags([.Incoming, .CountedAsIncoming]) } diff --git a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift new file mode 100644 index 0000000000..44935f0492 --- /dev/null +++ b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift @@ -0,0 +1,131 @@ +import Foundation + +final class MutableMessageHistoryThreadIndexView: MutablePostboxView { + final class Item { + let id: Int64 + let index: MessageIndex + var info: CodableEntry + var topMessage: Message? + + init( + id: Int64, + index: MessageIndex, + info: CodableEntry, + topMessage: Message? + ) { + self.id = id + self.index = index + self.info = info + self.topMessage = topMessage + } + } + + fileprivate let peerId: PeerId + fileprivate var peer: Peer? + fileprivate var items: [Item] = [] + + init(postbox: PostboxImpl, peerId: PeerId) { + self.peerId = peerId + + self.reload(postbox: postbox) + } + + private func reload(postbox: PostboxImpl) { + self.items.removeAll() + + self.peer = postbox.peerTable.get(self.peerId) + + for item in postbox.messageHistoryThreadIndexTable.getAll(peerId: self.peerId) { + self.items.append(Item( + id: item.threadId, + index: item.index, + info: item.info, + topMessage: postbox.getMessage(item.index.id) + )) + } + } + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { + var updated = false + + if transaction.updatedMessageThreadPeerIds.contains(self.peerId) { + self.reload(postbox: postbox) + updated = true + } + + return updated + } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + return false + } + + func immutableView() -> PostboxView { + return MessageHistoryThreadIndexView(self) + } +} + +public final class EngineMessageHistoryThread { + public final class Item: Equatable { + public let id: Int64 + public let index: MessageIndex + public let info: CodableEntry + public let topMessage: Message? + + public init( + id: Int64, + index: MessageIndex, + info: CodableEntry, + topMessage: Message? + ) { + self.id = id + self.index = index + self.info = info + self.topMessage = topMessage + } + + public static func ==(lhs: Item, rhs: Item) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.index != rhs.index { + return false + } + if lhs.info != rhs.info { + return false + } + if let lhsMessage = lhs.topMessage, let rhsMessage = rhs.topMessage { + if lhsMessage.index != rhsMessage.index { + return false + } + if lhsMessage.stableVersion != rhsMessage.stableVersion { + return false + } + } else if (lhs.topMessage == nil) != (rhs.topMessage == nil) { + return false + } + + return true + } + } +} + +public final class MessageHistoryThreadIndexView: PostboxView { + public let peer: Peer? + public let items: [EngineMessageHistoryThread.Item] + + init(_ view: MutableMessageHistoryThreadIndexView) { + self.peer = view.peer + + var items: [EngineMessageHistoryThread.Item] = [] + for item in view.items { + items.append(EngineMessageHistoryThread.Item( + id: item.id, + index: item.index, + info: item.info, + topMessage: item.topMessage + )) + } + self.items = items + } +} diff --git a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift index 6bf1a5b865..47aa7162fe 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift @@ -5,12 +5,19 @@ private func extractKey(_ key: ValueBoxKey) -> MessageIndex { } class MessageHistoryThreadsTable: Table { + struct ItemId: Hashable { + var peerId: PeerId + var threadId: Int64 + } + static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) } private let sharedKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4) + private(set) var updatedIds = Set() + override init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool) { super.init(valueBox: valueBox, table: table, useCaches: useCaches) } @@ -38,10 +45,12 @@ class MessageHistoryThreadsTable: Table { func add(threadId: Int64, index: MessageIndex) { self.valueBox.set(self.table, key: self.key(threadId: threadId, index: index, key: self.sharedKey), value: MemoryBuffer()) + self.updatedIds.insert(ItemId(peerId: index.id.peerId, threadId: threadId)) } func remove(threadId: Int64, index: MessageIndex) { self.valueBox.remove(self.table, key: self.key(threadId: threadId, index: index, key: self.sharedKey), secure: false) + self.updatedIds.insert(ItemId(peerId: index.id.peerId, threadId: threadId)) } func earlierIndices(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, count: Int) -> [MessageIndex] { @@ -82,6 +91,27 @@ class MessageHistoryThreadsTable: Table { return indices } + func getTop(peerId: PeerId, threadId: Int64, namespaces: Set) -> MessageIndex? { + var maxIndex: MessageIndex? + for namespace in namespaces { + var namespaceIndex: MessageIndex? + self.valueBox.range(self.table, start: self.upperBound(threadId: threadId, peerId: peerId, namespace: namespace), end: self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in + namespaceIndex = extractKey(key) + return false + }, limit: 1) + if let namespaceIndex = namespaceIndex { + if let maxIndexValue = maxIndex { + if namespaceIndex > maxIndexValue { + maxIndex = namespaceIndex + } + } else { + maxIndex = namespaceIndex + } + } + } + return maxIndex + } + func getMessageCountInRange(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex?, upperBound: MessageIndex?) -> Int { if let lowerBound = lowerBound { precondition(lowerBound.id.namespace == namespace) @@ -109,4 +139,10 @@ class MessageHistoryThreadsTable: Table { } return Int(self.valueBox.count(self.table, start: lowerBoundKey, end: upperBoundKey)) } + + override func beforeCommit() { + super.beforeCommit() + + self.updatedIds.removeAll() + } } diff --git a/submodules/Postbox/Sources/MessageThreadIndexTable.swift b/submodules/Postbox/Sources/MessageThreadIndexTable.swift new file mode 100644 index 0000000000..944dd9a507 --- /dev/null +++ b/submodules/Postbox/Sources/MessageThreadIndexTable.swift @@ -0,0 +1,180 @@ +import Foundation + +private func extractKey(_ key: ValueBoxKey) -> MessageIndex { + return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8), id: key.getInt32(8 + 8 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4)) +} + +class MessageHistoryThreadReverseIndexTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) + } + + private let sharedKey = ValueBoxKey(length: 8 + 8) + + override init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool) { + super.init(valueBox: valueBox, table: table, useCaches: useCaches) + } + + private func key(peerId: PeerId, threadId: Int64, key: ValueBoxKey) -> ValueBoxKey { + key.setInt64(0, value: peerId.toInt64()) + key.setInt64(8, value: threadId) + + return key + } + + func get(peerId: PeerId, threadId: Int64) -> MessageIndex? { + if let value = self.valueBox.get(self.table, key: self.key(peerId: peerId, threadId: threadId, key: self.sharedKey)) { + var result: MessageIndex? + withExtendedLifetime(value, { + let readBuffer = ReadBuffer(memoryBufferNoCopy: value) + var namespace: Int32 = 0 + readBuffer.read(&namespace, offset: 0, length: 4) + var id: Int32 = 0 + readBuffer.read(&id, offset: 0, length: 4) + var timestamp: Int32 = 0 + readBuffer.read(×tamp, offset: 0, length: 4) + result = MessageIndex(id: MessageId(peerId: peerId, namespace: namespace, id: id), timestamp: timestamp) + }) + return result + } else { + return nil + } + } + + func set(peerId: PeerId, threadId: Int64, timestamp: Int32, namespace: MessageId.Namespace, id: MessageId.Id, hasValue: Bool) { + if hasValue { + let buffer = WriteBuffer() + var namespace = namespace + buffer.write(&namespace, length: 4) + var id = id + buffer.write(&id, length: 4) + var timestamp = timestamp + buffer.write(×tamp, length: 4) + withExtendedLifetime(buffer, { + self.valueBox.set(self.table, key: self.key(peerId: peerId, threadId: threadId, key: self.sharedKey), value: buffer.readBufferNoCopy()) + }) + } else { + self.valueBox.remove(self.table, key: self.key(peerId: peerId, threadId: threadId, key: self.sharedKey), secure: false) + } + } +} + +class MessageHistoryThreadIndexTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) + } + + private let reverseIndexTable: MessageHistoryThreadReverseIndexTable + + private let sharedKey = ValueBoxKey(length: 8 + 4 + 8 + 4 + 4) + + private var updatedInfoItems: [MessageHistoryThreadsTable.ItemId: CodableEntry] = [:] + + init(valueBox: ValueBox, table: ValueBoxTable, reverseIndexTable: MessageHistoryThreadReverseIndexTable, useCaches: Bool) { + self.reverseIndexTable = reverseIndexTable + + super.init(valueBox: valueBox, table: table, useCaches: useCaches) + } + + private func key(peerId: PeerId, timestamp: Int32, threadId: Int64, namespace: MessageId.Namespace, id: MessageId.Id, key: ValueBoxKey) -> ValueBoxKey { + key.setInt64(0, value: peerId.toInt64()) + key.setInt32(8, value: timestamp) + key.setInt64(8 + 4, value: threadId) + key.setInt32(8 + 4 + 8, value: namespace) + key.setInt32(8 + 4 + 8 + 4, value: id) + + return key + } + + private static func extract(key: ValueBoxKey) -> (threadId: Int64, index: MessageIndex) { + return ( + threadId: key.getInt64(8 + 4), + index: MessageIndex( + id: MessageId( + peerId: PeerId(key.getInt64(0)), + namespace: key.getInt32(8 + 4 + 8), + id: key.getInt32(8 + 4 + 8 + 4) + ), + timestamp: key.getInt32(8) + ) + ) + } + + private func lowerBound(peerId: PeerId) -> ValueBoxKey { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: peerId.toInt64()) + return key + } + + private func upperBound(peerId: PeerId) -> ValueBoxKey { + return self.lowerBound(peerId: peerId).successor + } + + func set(peerId: PeerId, threadId: Int64, info: CodableEntry) { + self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] = info + } + + func replay(threadsTable: MessageHistoryThreadsTable, namespaces: Set, updatedIds: Set) -> Set { + var peerIds = Set() + for itemId in updatedIds.union(Set(self.updatedInfoItems.keys)) { + let topIndex = threadsTable.getTop(peerId: itemId.peerId, threadId: itemId.threadId, namespaces: namespaces) + let previousIndex = self.reverseIndexTable.get(peerId: itemId.peerId, threadId: itemId.threadId) + if topIndex != previousIndex || self.updatedInfoItems[itemId] != nil { + peerIds.insert(itemId.peerId) + + var info: ReadBuffer? + if let previousIndex = previousIndex { + let previousKey = self.key(peerId: itemId.peerId, timestamp: previousIndex.timestamp, threadId: itemId.threadId, namespace: previousIndex.id.namespace, id: previousIndex.id.id, key: self.sharedKey) + if let previousValue = self.valueBox.get(self.table, key: previousKey) { + if previousValue.length != 0 { + info = previousValue + } + } else { + assert(false) + } + self.valueBox.remove(self.table, key: previousKey, secure: true) + } + if let updatedInfo = self.updatedInfoItems[itemId] { + info = ReadBuffer(data: updatedInfo.data) + } + + if let topIndex = topIndex, let info = info { + if let previousIndex = previousIndex { + self.reverseIndexTable.set(peerId: itemId.peerId, threadId: itemId.threadId, timestamp: previousIndex.timestamp, namespace: previousIndex.id.namespace, id: previousIndex.id.id, hasValue: false) + } + + self.reverseIndexTable.set(peerId: itemId.peerId, threadId: itemId.threadId, timestamp: topIndex.timestamp, namespace: topIndex.id.namespace, id: topIndex.id.id, hasValue: true) + self.valueBox.set(self.table, key: self.key(peerId: itemId.peerId, timestamp: topIndex.timestamp, threadId: itemId.threadId, namespace: topIndex.id.namespace, id: topIndex.id.id, key: self.sharedKey), value: info) + } else { + if let previousIndex = previousIndex { + self.reverseIndexTable.set(peerId: itemId.peerId, threadId: itemId.threadId, timestamp: previousIndex.timestamp, namespace: previousIndex.id.namespace, id: previousIndex.id.id, hasValue: false) + self.valueBox.remove(self.table, key: self.key(peerId: itemId.peerId, timestamp: previousIndex.timestamp, threadId: itemId.threadId, namespace: previousIndex.id.namespace, id: previousIndex.id.id, key: self.sharedKey), secure: true) + } + } + } + } + + return peerIds + } + + func getAll(peerId: PeerId) -> [(threadId: Int64, index: MessageIndex, info: CodableEntry)] { + var result: [(threadId: Int64, index: MessageIndex, info: CodableEntry)] = [] + self.valueBox.range(self.table, start: self.upperBound(peerId: peerId), end: self.lowerBound(peerId: peerId), values: { key, value in + let keyData = MessageHistoryThreadIndexTable.extract(key: key) + if value.length == 0 { + return true + } + let info = CodableEntry(data: value.makeData()) + result.append((keyData.threadId, keyData.index, info)) + return true + }, limit: 100000) + + return result + } + + override func beforeCommit() { + super.beforeCommit() + + self.updatedInfoItems.removeAll() + } +} diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index d508b4ad20..853f624dbf 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1158,6 +1158,16 @@ public final class Transaction { assert(!self.disposed) self.postbox?.removePeerTimeoutAttributeEntry(peerId: peerId, timestamp: timestamp) } + + public func getMessageHistoryThreadIndex(peerId: PeerId) -> [(threadId: Int64, index: MessageIndex, info: CodableEntry)] { + assert(!self.disposed) + return self.postbox!.messageHistoryThreadIndexTable.getAll(peerId: peerId) + } + + public func setMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64, info: CodableEntry) { + assert(!self.disposed) + self.postbox!.messageHistoryThreadIndexTable.set(peerId: peerId, threadId: threadId, info: info) + } } public enum PostboxResult { @@ -1433,6 +1443,8 @@ final class PostboxImpl { let messageHistoryTagsTable: MessageHistoryTagsTable let messageHistoryThreadsTable: MessageHistoryThreadsTable let messageHistoryThreadHoleIndexTable: MessageHistoryThreadHoleIndexTable + let messageHistoryThreadReverseIndexTable: MessageHistoryThreadReverseIndexTable + let messageHistoryThreadIndexTable: MessageHistoryThreadIndexTable let globalMessageHistoryTagsTable: GlobalMessageHistoryTagsTable let localMessageHistoryTagsTable: LocalMessageHistoryTagsTable let peerChatStateTable: PeerChatStateTable @@ -1508,6 +1520,8 @@ final class PostboxImpl { self.messageHistoryTagsTable = MessageHistoryTagsTable(valueBox: self.valueBox, table: MessageHistoryTagsTable.tableSpec(12), useCaches: useCaches, seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) self.messageHistoryThreadsTable = MessageHistoryThreadsTable(valueBox: self.valueBox, table: MessageHistoryThreadsTable.tableSpec(62), useCaches: useCaches) self.messageHistoryThreadHoleIndexTable = MessageHistoryThreadHoleIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadHoleIndexTable.tableSpec(63), useCaches: useCaches, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration) + self.messageHistoryThreadReverseIndexTable = MessageHistoryThreadReverseIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadReverseIndexTable.tableSpec(69), useCaches: useCaches) + self.messageHistoryThreadIndexTable = MessageHistoryThreadIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadIndexTable.tableSpec(70), reverseIndexTable: self.messageHistoryThreadReverseIndexTable, useCaches: useCaches) self.globalMessageHistoryTagsTable = GlobalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(39), useCaches: useCaches) self.localMessageHistoryTagsTable = LocalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(52), useCaches: useCaches) self.messageHistoryIndexTable = MessageHistoryIndexTable(valueBox: self.valueBox, table: MessageHistoryIndexTable.tableSpec(4), useCaches: useCaches, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, globalMessageIdsTable: self.globalMessageIdsTable, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration) @@ -1564,6 +1578,8 @@ final class PostboxImpl { tables.append(self.messageHistoryTagsTable) tables.append(self.messageHistoryThreadsTable) tables.append(self.messageHistoryThreadHoleIndexTable) + tables.append(self.messageHistoryThreadReverseIndexTable) + tables.append(self.messageHistoryThreadIndexTable) tables.append(self.globalMessageHistoryTagsTable) tables.append(self.localMessageHistoryTagsTable) tables.append(self.messageHistoryIndexTable) @@ -2002,13 +2018,15 @@ final class PostboxImpl { self.peerTable.commitDependentTables() + let updatedMessageThreadPeerIds = self.messageHistoryThreadIndexTable.replay(threadsTable: self.messageHistoryThreadsTable, namespaces: self.seedConfiguration.chatMessagesNamespaces, updatedIds: self.messageHistoryThreadsTable.updatedIds) + if self.currentNeedsReindexUnreadCounters { self.reindexUnreadCounters(currentTransaction: currentTransaction) } let updatedPeerTimeoutAttributes = self.peerTimeoutPropertiesTable.hasUpdates - let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes) + let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes, updatedMessageThreadPeerIds: updatedMessageThreadPeerIds) var updatedTransactionState: Int64? var updatedMasterClientId: Int64? if !transaction.isEmpty { diff --git a/submodules/Postbox/Sources/PostboxTransaction.swift b/submodules/Postbox/Sources/PostboxTransaction.swift index 6e96ccf0d8..66d90c4aea 100644 --- a/submodules/Postbox/Sources/PostboxTransaction.swift +++ b/submodules/Postbox/Sources/PostboxTransaction.swift @@ -44,6 +44,7 @@ final class PostboxTransaction { let updatedFailedMessageIds: Set let updatedGlobalNotificationSettings: Bool let updatedPeerTimeoutAttributes: Bool + let updatedMessageThreadPeerIds: Set var isEmpty: Bool { if currentUpdatedState != nil { @@ -175,10 +176,13 @@ final class PostboxTransaction { if self.updatedPeerTimeoutAttributes { return false } + if !self.updatedMessageThreadPeerIds.isEmpty { + return false + } return true } - init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: Set, currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState], currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, replacedAdditionalChatListItems: [AdditionalChatListItem]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set, updatedFailedMessageIds: Set, updatedGlobalNotificationSettings: Bool, updatedPeerTimeoutAttributes: Bool) { + init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: Set, currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState], currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, replacedAdditionalChatListItems: [AdditionalChatListItem]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set, updatedFailedMessageIds: Set, updatedGlobalNotificationSettings: Bool, updatedPeerTimeoutAttributes: Bool, updatedMessageThreadPeerIds: Set) { self.currentUpdatedState = currentUpdatedState self.currentPeerHoleOperations = currentPeerHoleOperations self.currentOperationsByPeerId = currentOperationsByPeerId @@ -221,5 +225,6 @@ final class PostboxTransaction { self.updatedFailedMessageIds = updatedFailedMessageIds self.updatedGlobalNotificationSettings = updatedGlobalNotificationSettings self.updatedPeerTimeoutAttributes = updatedPeerTimeoutAttributes + self.updatedMessageThreadPeerIds = updatedMessageThreadPeerIds } } diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index c07da381f0..7f00923709 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -38,6 +38,7 @@ public enum PostboxViewKey: Hashable { case isContact(id: PeerId) case chatListIndex(id: PeerId) case peerTimeoutAttributes + case messageHistoryThreadIndex(id: PeerId) public func hash(into hasher: inout Hasher) { switch self { @@ -124,6 +125,8 @@ public enum PostboxViewKey: Hashable { hasher.combine(id) case .peerTimeoutAttributes: hasher.combine(17) + case let .messageHistoryThreadIndex(id): + hasher.combine(id) } } @@ -351,6 +354,12 @@ public enum PostboxViewKey: Hashable { } else { return false } + case let .messageHistoryThreadIndex(id): + if case .messageHistoryThreadIndex(id) = rhs { + return true + } else { + return false + } } } } @@ -431,5 +440,7 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutableChatListIndexView(postbox: postbox, id: id) case .peerTimeoutAttributes: return MutablePeerTimeoutAttributesView(postbox: postbox) + case let .messageHistoryThreadIndex(id): + return MutableMessageHistoryThreadIndexView(postbox: postbox, peerId: id) } } diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 941e41d88e..f312e97552 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -218,8 +218,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { var items: [ChatListItem] = [] - let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) @@ -239,9 +239,9 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView return ChatListItem( presentationData: chatListPresentationData, context: self.context, - peerGroupId: .root, + chatListLocation: .chatList(groupId: .root), filterData: nil, - index: ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp)), + index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), content: .peer( messages: [ EngineMessage( @@ -269,6 +269,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView ) ], peer: EngineRenderedPeer(peer: peer), + threadInfo: nil, combinedReadState: EnginePeerReadCounters(incomingReadId: 1000, outgoingReadId: 1000, count: unreadCount, markedUnread: false), isRemovedFromTotalUnreadCount: false, presence: presenceTimestamp.flatMap { presenceTimestamp in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 129136fc5a..a71488a3af 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -838,8 +838,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { var items: [ChatListItem] = [] - let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in @@ -859,9 +859,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate return ChatListItem( presentationData: chatListPresentationData, context: self.context, - peerGroupId: .root, + chatListLocation: .chatList(groupId: .root), filterData: nil, - index: ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp)), + index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), content: .peer( messages: [ EngineMessage( @@ -889,6 +889,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate ) ], peer: EngineRenderedPeer(peer: peer), + threadInfo: nil, combinedReadState: EnginePeerReadCounters(incomingReadId: 1000, outgoingReadId: 1000, count: unreadCount, markedUnread: false), isRemovedFromTotalUnreadCount: false, presence: presenceTimestamp.flatMap { presenceTimestamp in diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 13bc2b6cc1..7bf64fec62 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -362,8 +362,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { var items: [ChatListItem] = [] - let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in @@ -382,9 +382,9 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { return ChatListItem( presentationData: chatListPresentationData, context: self.context, - peerGroupId: .root, + chatListLocation: .chatList(groupId: .root), filterData: nil, - index: ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp)), + index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), content: .peer( messages: [ EngineMessage( @@ -412,6 +412,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { ) ], peer: EngineRenderedPeer(peer: peer), + threadInfo: nil, combinedReadState: EnginePeerReadCounters(incomingReadId: 1000, outgoingReadId: 1000, count: unreadCount, markedUnread: false), isRemovedFromTotalUnreadCount: false, presence: presenceTimestamp.flatMap { presenceTimestamp in diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index e3d8f921fc..dc9b72b62f 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -222,6 +222,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-207944868] = { return Api.FileHash.parse_fileHash($0) } dict[-11252123] = { return Api.Folder.parse_folder($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } + dict[1885902651] = { return Api.ForumTopic.parse_forumTopic($0) } dict[-1107729093] = { return Api.Game.parse_game($0) } dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) } dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } @@ -445,6 +446,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[455635795] = { return Api.MessageAction.parse_messageActionSecureValuesSentMe($0) } dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } + dict[1873254060] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } + dict[-2113245653] = { return Api.MessageAction.parse_messageActionTopicEditIcon($0) } + dict[-838130739] = { return Api.MessageAction.parse_messageActionTopicEditTitle($0) } dict[-1262252875] = { return Api.MessageAction.parse_messageActionWebViewDataSent($0) } dict[1205698681] = { return Api.MessageAction.parse_messageActionWebViewDataSentMe($0) } dict[546203849] = { return Api.MessageEntity.parse_inputMessageEntityMentionName($0) } @@ -1003,6 +1007,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1634752813] = { return Api.messages.FavedStickers.parse_favedStickersNotModified($0) } dict[-1103615738] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) } dict[-958657434] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) } + dict[913709011] = { return Api.messages.ForumTopics.parse_forumTopics($0) } dict[-1963942446] = { return Api.messages.FoundStickerSets.parse_foundStickerSets($0) } dict[223655517] = { return Api.messages.FoundStickerSets.parse_foundStickerSetsNotModified($0) } dict[-1707344487] = { return Api.messages.HighScores.parse_highScores($0) } @@ -1281,6 +1286,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.FolderPeer: _1.serialize(buffer, boxed) + case let _1 as Api.ForumTopic: + _1.serialize(buffer, boxed) case let _1 as Api.Game: _1.serialize(buffer, boxed) case let _1 as Api.GeoPoint: @@ -1779,6 +1786,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.FeaturedStickers: _1.serialize(buffer, boxed) + case let _1 as Api.messages.ForumTopics: + _1.serialize(buffer, boxed) case let _1 as Api.messages.FoundStickerSets: _1.serialize(buffer, boxed) case let _1 as Api.messages.HighScores: diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 1c514a96a4..7676164a2b 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -1,3 +1,155 @@ +public extension Api { + enum InputStickerSet: TypeConstructorDescription { + case inputStickerSetAnimatedEmoji + case inputStickerSetAnimatedEmojiAnimations + case inputStickerSetDice(emoticon: String) + case inputStickerSetEmojiDefaultStatuses + case inputStickerSetEmojiGenericAnimations + case inputStickerSetEmpty + case inputStickerSetID(id: Int64, accessHash: Int64) + case inputStickerSetPremiumGifts + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetAnimatedEmoji: + if boxed { + buffer.appendInt32(42402760) + } + + break + case .inputStickerSetAnimatedEmojiAnimations: + if boxed { + buffer.appendInt32(215889721) + } + + break + case .inputStickerSetDice(let emoticon): + if boxed { + buffer.appendInt32(-427863538) + } + serializeString(emoticon, buffer: buffer, boxed: false) + break + case .inputStickerSetEmojiDefaultStatuses: + if boxed { + buffer.appendInt32(701560302) + } + + break + case .inputStickerSetEmojiGenericAnimations: + if boxed { + buffer.appendInt32(80008398) + } + + break + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + + break + case .inputStickerSetID(let id, let accessHash): + if boxed { + buffer.appendInt32(-1645763991) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputStickerSetPremiumGifts: + if boxed { + buffer.appendInt32(-930399486) + } + + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputStickerSetAnimatedEmoji: + return ("inputStickerSetAnimatedEmoji", []) + case .inputStickerSetAnimatedEmojiAnimations: + return ("inputStickerSetAnimatedEmojiAnimations", []) + case .inputStickerSetDice(let emoticon): + return ("inputStickerSetDice", [("emoticon", String(describing: emoticon))]) + case .inputStickerSetEmojiDefaultStatuses: + return ("inputStickerSetEmojiDefaultStatuses", []) + case .inputStickerSetEmojiGenericAnimations: + return ("inputStickerSetEmojiGenericAnimations", []) + case .inputStickerSetEmpty: + return ("inputStickerSetEmpty", []) + case .inputStickerSetID(let id, let accessHash): + return ("inputStickerSetID", [("id", String(describing: id)), ("accessHash", String(describing: accessHash))]) + case .inputStickerSetPremiumGifts: + return ("inputStickerSetPremiumGifts", []) + case .inputStickerSetShortName(let shortName): + return ("inputStickerSetShortName", [("shortName", String(describing: shortName))]) + } + } + + public static func parse_inputStickerSetAnimatedEmoji(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetAnimatedEmoji + } + public static func parse_inputStickerSetAnimatedEmojiAnimations(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetAnimatedEmojiAnimations + } + public static func parse_inputStickerSetDice(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputStickerSet.inputStickerSetDice(emoticon: _1!) + } + else { + return nil + } + } + public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses + } + public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations + } + public static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmpty + } + public static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputStickerSet.inputStickerSetID(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputStickerSetPremiumGifts(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetPremiumGifts + } + public static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputStickerSetItem: TypeConstructorDescription { case inputStickerSetItem(flags: Int32, document: Api.InputDocument, emoji: String, maskCoords: Api.MaskCoords?) @@ -904,401 +1056,3 @@ public extension Api { } } -public extension Api { - indirect enum KeyboardButton: TypeConstructorDescription { - case inputKeyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, bot: Api.InputUser) - case inputKeyboardButtonUserProfile(text: String, userId: Api.InputUser) - case keyboardButton(text: String) - case keyboardButtonBuy(text: String) - case keyboardButtonCallback(flags: Int32, text: String, data: Buffer) - case keyboardButtonGame(text: String) - case keyboardButtonRequestGeoLocation(text: String) - case keyboardButtonRequestPhone(text: String) - case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String) - case keyboardButtonSimpleWebView(text: String, url: String) - case keyboardButtonSwitchInline(flags: Int32, text: String, query: String) - case keyboardButtonUrl(text: String, url: String) - case keyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, buttonId: Int32) - case keyboardButtonUserProfile(text: String, userId: Int64) - case keyboardButtonWebView(text: String, url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot): - if boxed { - buffer.appendInt32(-802258988) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(fwdText!, buffer: buffer, boxed: false)} - serializeString(url, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - break - case .inputKeyboardButtonUserProfile(let text, let userId): - if boxed { - buffer.appendInt32(-376962181) - } - serializeString(text, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - break - case .keyboardButton(let text): - if boxed { - buffer.appendInt32(-1560655744) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonBuy(let text): - if boxed { - buffer.appendInt32(-1344716869) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonCallback(let flags, let text, let data): - if boxed { - buffer.appendInt32(901503851) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - break - case .keyboardButtonGame(let text): - if boxed { - buffer.appendInt32(1358175439) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestGeoLocation(let text): - if boxed { - buffer.appendInt32(-59151553) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestPhone(let text): - if boxed { - buffer.appendInt32(-1318425559) - } - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestPoll(let flags, let quiz, let text): - if boxed { - buffer.appendInt32(-1144565411) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {quiz!.serialize(buffer, true)} - serializeString(text, buffer: buffer, boxed: false) - break - case .keyboardButtonSimpleWebView(let text, let url): - if boxed { - buffer.appendInt32(-1598009252) - } - serializeString(text, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - case .keyboardButtonSwitchInline(let flags, let text, let query): - if boxed { - buffer.appendInt32(90744648) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - serializeString(query, buffer: buffer, boxed: false) - break - case .keyboardButtonUrl(let text, let url): - if boxed { - buffer.appendInt32(629866245) - } - serializeString(text, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - case .keyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let buttonId): - if boxed { - buffer.appendInt32(280464681) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(text, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(fwdText!, buffer: buffer, boxed: false)} - serializeString(url, buffer: buffer, boxed: false) - serializeInt32(buttonId, buffer: buffer, boxed: false) - break - case .keyboardButtonUserProfile(let text, let userId): - if boxed { - buffer.appendInt32(814112961) - } - serializeString(text, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - break - case .keyboardButtonWebView(let text, let url): - if boxed { - buffer.appendInt32(326529584) - } - serializeString(text, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot): - return ("inputKeyboardButtonUrlAuth", [("flags", String(describing: flags)), ("text", String(describing: text)), ("fwdText", String(describing: fwdText)), ("url", String(describing: url)), ("bot", String(describing: bot))]) - case .inputKeyboardButtonUserProfile(let text, let userId): - return ("inputKeyboardButtonUserProfile", [("text", String(describing: text)), ("userId", String(describing: userId))]) - case .keyboardButton(let text): - return ("keyboardButton", [("text", String(describing: text))]) - case .keyboardButtonBuy(let text): - return ("keyboardButtonBuy", [("text", String(describing: text))]) - case .keyboardButtonCallback(let flags, let text, let data): - return ("keyboardButtonCallback", [("flags", String(describing: flags)), ("text", String(describing: text)), ("data", String(describing: data))]) - case .keyboardButtonGame(let text): - return ("keyboardButtonGame", [("text", String(describing: text))]) - case .keyboardButtonRequestGeoLocation(let text): - return ("keyboardButtonRequestGeoLocation", [("text", String(describing: text))]) - case .keyboardButtonRequestPhone(let text): - return ("keyboardButtonRequestPhone", [("text", String(describing: text))]) - case .keyboardButtonRequestPoll(let flags, let quiz, let text): - return ("keyboardButtonRequestPoll", [("flags", String(describing: flags)), ("quiz", String(describing: quiz)), ("text", String(describing: text))]) - case .keyboardButtonSimpleWebView(let text, let url): - return ("keyboardButtonSimpleWebView", [("text", String(describing: text)), ("url", String(describing: url))]) - case .keyboardButtonSwitchInline(let flags, let text, let query): - return ("keyboardButtonSwitchInline", [("flags", String(describing: flags)), ("text", String(describing: text)), ("query", String(describing: query))]) - case .keyboardButtonUrl(let text, let url): - return ("keyboardButtonUrl", [("text", String(describing: text)), ("url", String(describing: url))]) - case .keyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let buttonId): - return ("keyboardButtonUrlAuth", [("flags", String(describing: flags)), ("text", String(describing: text)), ("fwdText", String(describing: fwdText)), ("url", String(describing: url)), ("buttonId", String(describing: buttonId))]) - case .keyboardButtonUserProfile(let text, let userId): - return ("keyboardButtonUserProfile", [("text", String(describing: text)), ("userId", String(describing: userId))]) - case .keyboardButtonWebView(let text, let url): - return ("keyboardButtonWebView", [("text", String(describing: text)), ("url", String(describing: url))]) - } - } - - public static func parse_inputKeyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: String? - _4 = parseString(reader) - var _5: Api.InputUser? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.InputUser - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.inputKeyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, bot: _5!) - } - else { - return nil - } - } - public static func parse_inputKeyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: Api.InputUser? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputUser - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.inputKeyboardButtonUserProfile(text: _1!, userId: _2!) - } - else { - return nil - } - } - public static func parse_keyboardButton(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButton(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonBuy(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonBuy(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Buffer? - _3 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonCallback(flags: _1!, text: _2!, data: _3!) - } - else { - return nil - } - } - public static func parse_keyboardButtonGame(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonGame(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestGeoLocation(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonRequestGeoLocation(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestPhone(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonRequestPhone(text: _1!) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestPoll(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonRequestPoll(flags: _1!, quiz: _2, text: _3!) - } - else { - return nil - } - } - public static func parse_keyboardButtonSimpleWebView(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonSimpleWebView(text: _1!, url: _2!) - } - else { - return nil - } - } - public static func parse_keyboardButtonSwitchInline(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonSwitchInline(flags: _1!, text: _2!, query: _3!) - } - else { - return nil - } - } - public static func parse_keyboardButtonUrl(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonUrl(text: _1!, url: _2!) - } - else { - return nil - } - } - public static func parse_keyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.keyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, buttonId: _5!) - } - else { - return nil - } - } - public static func parse_keyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonUserProfile(text: _1!, userId: _2!) - } - else { - return nil - } - } - public static func parse_keyboardButtonWebView(_ reader: BufferReader) -> KeyboardButton? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonWebView(text: _1!, url: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index e89df59cf7..b6366ce19f 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -1,3 +1,401 @@ +public extension Api { + indirect enum KeyboardButton: TypeConstructorDescription { + case inputKeyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, bot: Api.InputUser) + case inputKeyboardButtonUserProfile(text: String, userId: Api.InputUser) + case keyboardButton(text: String) + case keyboardButtonBuy(text: String) + case keyboardButtonCallback(flags: Int32, text: String, data: Buffer) + case keyboardButtonGame(text: String) + case keyboardButtonRequestGeoLocation(text: String) + case keyboardButtonRequestPhone(text: String) + case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String) + case keyboardButtonSimpleWebView(text: String, url: String) + case keyboardButtonSwitchInline(flags: Int32, text: String, query: String) + case keyboardButtonUrl(text: String, url: String) + case keyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, buttonId: Int32) + case keyboardButtonUserProfile(text: String, userId: Int64) + case keyboardButtonWebView(text: String, url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot): + if boxed { + buffer.appendInt32(-802258988) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(fwdText!, buffer: buffer, boxed: false)} + serializeString(url, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + break + case .inputKeyboardButtonUserProfile(let text, let userId): + if boxed { + buffer.appendInt32(-376962181) + } + serializeString(text, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + break + case .keyboardButton(let text): + if boxed { + buffer.appendInt32(-1560655744) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonBuy(let text): + if boxed { + buffer.appendInt32(-1344716869) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonCallback(let flags, let text, let data): + if boxed { + buffer.appendInt32(901503851) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + break + case .keyboardButtonGame(let text): + if boxed { + buffer.appendInt32(1358175439) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestGeoLocation(let text): + if boxed { + buffer.appendInt32(-59151553) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestPhone(let text): + if boxed { + buffer.appendInt32(-1318425559) + } + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestPoll(let flags, let quiz, let text): + if boxed { + buffer.appendInt32(-1144565411) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {quiz!.serialize(buffer, true)} + serializeString(text, buffer: buffer, boxed: false) + break + case .keyboardButtonSimpleWebView(let text, let url): + if boxed { + buffer.appendInt32(-1598009252) + } + serializeString(text, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .keyboardButtonSwitchInline(let flags, let text, let query): + if boxed { + buffer.appendInt32(90744648) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + serializeString(query, buffer: buffer, boxed: false) + break + case .keyboardButtonUrl(let text, let url): + if boxed { + buffer.appendInt32(629866245) + } + serializeString(text, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .keyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let buttonId): + if boxed { + buffer.appendInt32(280464681) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(text, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(fwdText!, buffer: buffer, boxed: false)} + serializeString(url, buffer: buffer, boxed: false) + serializeInt32(buttonId, buffer: buffer, boxed: false) + break + case .keyboardButtonUserProfile(let text, let userId): + if boxed { + buffer.appendInt32(814112961) + } + serializeString(text, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + break + case .keyboardButtonWebView(let text, let url): + if boxed { + buffer.appendInt32(326529584) + } + serializeString(text, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot): + return ("inputKeyboardButtonUrlAuth", [("flags", String(describing: flags)), ("text", String(describing: text)), ("fwdText", String(describing: fwdText)), ("url", String(describing: url)), ("bot", String(describing: bot))]) + case .inputKeyboardButtonUserProfile(let text, let userId): + return ("inputKeyboardButtonUserProfile", [("text", String(describing: text)), ("userId", String(describing: userId))]) + case .keyboardButton(let text): + return ("keyboardButton", [("text", String(describing: text))]) + case .keyboardButtonBuy(let text): + return ("keyboardButtonBuy", [("text", String(describing: text))]) + case .keyboardButtonCallback(let flags, let text, let data): + return ("keyboardButtonCallback", [("flags", String(describing: flags)), ("text", String(describing: text)), ("data", String(describing: data))]) + case .keyboardButtonGame(let text): + return ("keyboardButtonGame", [("text", String(describing: text))]) + case .keyboardButtonRequestGeoLocation(let text): + return ("keyboardButtonRequestGeoLocation", [("text", String(describing: text))]) + case .keyboardButtonRequestPhone(let text): + return ("keyboardButtonRequestPhone", [("text", String(describing: text))]) + case .keyboardButtonRequestPoll(let flags, let quiz, let text): + return ("keyboardButtonRequestPoll", [("flags", String(describing: flags)), ("quiz", String(describing: quiz)), ("text", String(describing: text))]) + case .keyboardButtonSimpleWebView(let text, let url): + return ("keyboardButtonSimpleWebView", [("text", String(describing: text)), ("url", String(describing: url))]) + case .keyboardButtonSwitchInline(let flags, let text, let query): + return ("keyboardButtonSwitchInline", [("flags", String(describing: flags)), ("text", String(describing: text)), ("query", String(describing: query))]) + case .keyboardButtonUrl(let text, let url): + return ("keyboardButtonUrl", [("text", String(describing: text)), ("url", String(describing: url))]) + case .keyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let buttonId): + return ("keyboardButtonUrlAuth", [("flags", String(describing: flags)), ("text", String(describing: text)), ("fwdText", String(describing: fwdText)), ("url", String(describing: url)), ("buttonId", String(describing: buttonId))]) + case .keyboardButtonUserProfile(let text, let userId): + return ("keyboardButtonUserProfile", [("text", String(describing: text)), ("userId", String(describing: userId))]) + case .keyboardButtonWebView(let text, let url): + return ("keyboardButtonWebView", [("text", String(describing: text)), ("url", String(describing: url))]) + } + } + + public static func parse_inputKeyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } + var _4: String? + _4 = parseString(reader) + var _5: Api.InputUser? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.InputUser + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.KeyboardButton.inputKeyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, bot: _5!) + } + else { + return nil + } + } + public static func parse_inputKeyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + var _2: Api.InputUser? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputUser + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.KeyboardButton.inputKeyboardButtonUserProfile(text: _1!, userId: _2!) + } + else { + return nil + } + } + public static func parse_keyboardButton(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.KeyboardButton.keyboardButton(text: _1!) + } + else { + return nil + } + } + public static func parse_keyboardButtonBuy(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.KeyboardButton.keyboardButtonBuy(text: _1!) + } + else { + return nil + } + } + public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButtonCallback(flags: _1!, text: _2!, data: _3!) + } + else { + return nil + } + } + public static func parse_keyboardButtonGame(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.KeyboardButton.keyboardButtonGame(text: _1!) + } + else { + return nil + } + } + public static func parse_keyboardButtonRequestGeoLocation(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.KeyboardButton.keyboardButtonRequestGeoLocation(text: _1!) + } + else { + return nil + } + } + public static func parse_keyboardButtonRequestPhone(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.KeyboardButton.keyboardButtonRequestPhone(text: _1!) + } + else { + return nil + } + } + public static func parse_keyboardButtonRequestPoll(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButtonRequestPoll(flags: _1!, quiz: _2, text: _3!) + } + else { + return nil + } + } + public static func parse_keyboardButtonSimpleWebView(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.KeyboardButton.keyboardButtonSimpleWebView(text: _1!, url: _2!) + } + else { + return nil + } + } + public static func parse_keyboardButtonSwitchInline(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButtonSwitchInline(flags: _1!, text: _2!, query: _3!) + } + else { + return nil + } + } + public static func parse_keyboardButtonUrl(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.KeyboardButton.keyboardButtonUrl(text: _1!, url: _2!) + } + else { + return nil + } + } + public static func parse_keyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.KeyboardButton.keyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, buttonId: _5!) + } + else { + return nil + } + } + public static func parse_keyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.KeyboardButton.keyboardButtonUserProfile(text: _1!, userId: _2!) + } + else { + return nil + } + } + public static func parse_keyboardButtonWebView(_ reader: BufferReader) -> KeyboardButton? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.KeyboardButton.keyboardButtonWebView(text: _1!, url: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum KeyboardButtonRow: TypeConstructorDescription { case keyboardButtonRow(buttons: [Api.KeyboardButton]) @@ -621,6 +1019,9 @@ public extension Api { case messageActionSecureValuesSentMe(values: [Api.SecureValue], credentials: Api.SecureCredentialsEncrypted) case messageActionSetChatTheme(emoticon: String) case messageActionSetMessagesTTL(period: Int32) + case messageActionTopicCreate(flags: Int32, title: String, iconEmojiId: Int64?) + case messageActionTopicEditIcon(emojiDocumentId: Int64) + case messageActionTopicEditTitle(title: String) case messageActionWebViewDataSent(text: String) case messageActionWebViewDataSentMe(text: String, data: String) @@ -856,6 +1257,26 @@ public extension Api { } serializeInt32(period, buffer: buffer, boxed: false) break + case .messageActionTopicCreate(let flags, let title, let iconEmojiId): + if boxed { + buffer.appendInt32(1873254060) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} + break + case .messageActionTopicEditIcon(let emojiDocumentId): + if boxed { + buffer.appendInt32(-2113245653) + } + serializeInt64(emojiDocumentId, buffer: buffer, boxed: false) + break + case .messageActionTopicEditTitle(let title): + if boxed { + buffer.appendInt32(-838130739) + } + serializeString(title, buffer: buffer, boxed: false) + break case .messageActionWebViewDataSent(let text): if boxed { buffer.appendInt32(-1262252875) @@ -936,6 +1357,12 @@ public extension Api { return ("messageActionSetChatTheme", [("emoticon", String(describing: emoticon))]) case .messageActionSetMessagesTTL(let period): return ("messageActionSetMessagesTTL", [("period", String(describing: period))]) + case .messageActionTopicCreate(let flags, let title, let iconEmojiId): + return ("messageActionTopicCreate", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId))]) + case .messageActionTopicEditIcon(let emojiDocumentId): + return ("messageActionTopicEditIcon", [("emojiDocumentId", String(describing: emojiDocumentId))]) + case .messageActionTopicEditTitle(let title): + return ("messageActionTopicEditTitle", [("title", String(describing: title))]) case .messageActionWebViewDataSent(let text): return ("messageActionWebViewDataSent", [("text", String(describing: text))]) case .messageActionWebViewDataSentMe(let text, let data): @@ -1330,6 +1757,45 @@ public extension Api { return nil } } + public static func parse_messageActionTopicCreate(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt64() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionTopicCreate(flags: _1!, title: _2!, iconEmojiId: _3) + } + else { + return nil + } + } + public static func parse_messageActionTopicEditIcon(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionTopicEditIcon(emojiDocumentId: _1!) + } + else { + return nil + } + } + public static func parse_messageActionTopicEditTitle(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionTopicEditTitle(title: _1!) + } + else { + return nil + } + } public static func parse_messageActionWebViewDataSent(_ reader: BufferReader) -> MessageAction? { var _1: String? _1 = parseString(reader) diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 549fe0e3c0..e3b58abe33 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -868,6 +868,90 @@ public extension Api.messages { } } +public extension Api.messages { + enum ForumTopics: TypeConstructorDescription { + case forumTopics(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): + if boxed { + buffer.appendInt32(913709011) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(topics.count)) + for item in topics { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + serializeInt32(pts, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): + return ("forumTopics", [("flags", String(describing: flags)), ("count", String(describing: count)), ("topics", String(describing: topics)), ("messages", String(describing: messages)), ("chats", String(describing: chats)), ("users", String(describing: users)), ("pts", String(describing: pts))]) + } + } + + public static func parse_forumTopics(_ reader: BufferReader) -> ForumTopics? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.ForumTopic]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) + } + var _4: [Api.Message]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _7: Int32? + _7 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.messages.ForumTopics.forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!) + } + else { + return nil + } + } + + } +} public extension Api.messages { enum FoundStickerSets: TypeConstructorDescription { case foundStickerSets(hash: Int64, sets: [Api.StickerSetCovered]) diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 9220ea2ffa..5069b67b53 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1819,6 +1819,26 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func createForumTopic(flags: Int32, channel: Api.InputChannel, title: String, iconEmojiId: Int64?, randomId: Int64, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1623145417) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {sendAs!.serialize(buffer, true)} + return (FunctionDescription(name: "channels.createForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("randomId", String(describing: randomId)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.channels { static func deleteChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -1939,6 +1959,40 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func editForumIcon(channel: Api.InputChannel, topicId: Int32, emojiDocumentId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1439605469) + channel.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + serializeInt64(emojiDocumentId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.editForumIcon", parameters: [("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("emojiDocumentId", String(describing: emojiDocumentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func editForumTitle(channel: Api.InputChannel, topicId: Int32, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2004764239) + channel.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.editForumTitle", parameters: [("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.channels { static func editLocation(channel: Api.InputChannel, geoPoint: Api.InputGeoPoint, address: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2065,6 +2119,47 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func getForumTopics(flags: Int32, channel: Api.InputChannel, q: String?, offsetDate: Int32, offsetId: Int32, offsetTopic: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(233136337) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeString(q!, buffer: buffer, boxed: false)} + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetTopic, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getForumTopics", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("q", String(describing: q)), ("offsetDate", String(describing: offsetDate)), ("offsetId", String(describing: offsetId)), ("offsetTopic", String(describing: offsetTopic)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in + let reader = BufferReader(buffer) + var result: Api.messages.ForumTopics? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics + } + return result + }) + } +} +public extension Api.functions.channels { + static func getForumTopicsByID(channel: Api.InputChannel, topics: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1333584199) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(topics.count)) + for item in topics { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.getForumTopicsByID", parameters: [("channel", String(describing: channel)), ("topics", String(describing: topics))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in + let reader = BufferReader(buffer) + var result: Api.messages.ForumTopics? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics + } + return result + }) + } +} public extension Api.functions.channels { static func getFullChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2349,6 +2444,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1540781271) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleForum", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.channels { static func toggleJoinRequest(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index 91062ecf1a..96cffb5c4b 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -1093,65 +1093,65 @@ public extension Api { } } public extension Api { - enum Game: TypeConstructorDescription { - case game(flags: Int32, id: Int64, accessHash: Int64, shortName: String, title: String, description: String, photo: Api.Photo, document: Api.Document?) + enum ForumTopic: TypeConstructorDescription { + case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .game(let flags, let id, let accessHash, let shortName, let title, let description, let photo, let document): + case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount): if boxed { - buffer.appendInt32(-1107729093) + buffer.appendInt32(1885902651) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeString(shortName, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - photo.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} + serializeInt32(topMessage, buffer: buffer, boxed: false) + serializeInt32(readInboxMaxId, buffer: buffer, boxed: false) + serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false) + serializeInt32(unreadCount, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .game(let flags, let id, let accessHash, let shortName, let title, let description, let photo, let document): - return ("game", [("flags", String(describing: flags)), ("id", String(describing: id)), ("accessHash", String(describing: accessHash)), ("shortName", String(describing: shortName)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("document", String(describing: document))]) + case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount): + return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount))]) } } - public static func parse_game(_ reader: BufferReader) -> Game? { + public static func parse_forumTopic(_ reader: BufferReader) -> ForumTopic? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() var _4: String? _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: Api.Photo? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Photo - } - var _8: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Document - } } + var _5: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Int32? + _9 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Game.game(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8) + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconEmojiId: _5, topMessage: _6!, readInboxMaxId: _7!, readOutboxMaxId: _8!, unreadCount: _9!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index da024d8be8..7177c5d984 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -1,3 +1,71 @@ +public extension Api { + enum Game: TypeConstructorDescription { + case game(flags: Int32, id: Int64, accessHash: Int64, shortName: String, title: String, description: String, photo: Api.Photo, document: Api.Document?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .game(let flags, let id, let accessHash, let shortName, let title, let description, let photo, let document): + if boxed { + buffer.appendInt32(-1107729093) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeString(shortName, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + photo.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .game(let flags, let id, let accessHash, let shortName, let title, let description, let photo, let document): + return ("game", [("flags", String(describing: flags)), ("id", String(describing: id)), ("accessHash", String(describing: accessHash)), ("shortName", String(describing: shortName)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("document", String(describing: document))]) + } + } + + public static func parse_game(_ reader: BufferReader) -> Game? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: Api.Photo? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Photo + } + var _8: Api.Document? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Document + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Game.game(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8) + } + else { + return nil + } + } + + } +} public extension Api { enum GeoPoint: TypeConstructorDescription { case geoPoint(flags: Int32, long: Double, lat: Double, accessHash: Int64, accuracyRadius: Int32?) @@ -1264,85 +1332,3 @@ public extension Api { } } -public extension Api { - indirect enum InputChannel: TypeConstructorDescription { - case inputChannel(channelId: Int64, accessHash: Int64) - case inputChannelEmpty - case inputChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputChannel(let channelId, let accessHash): - if boxed { - buffer.appendInt32(-212145112) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputChannelEmpty: - if boxed { - buffer.appendInt32(-292807034) - } - - break - case .inputChannelFromMessage(let peer, let msgId, let channelId): - if boxed { - buffer.appendInt32(1536380829) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputChannel(let channelId, let accessHash): - return ("inputChannel", [("channelId", String(describing: channelId)), ("accessHash", String(describing: accessHash))]) - case .inputChannelEmpty: - return ("inputChannelEmpty", []) - case .inputChannelFromMessage(let peer, let msgId, let channelId): - return ("inputChannelFromMessage", [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("channelId", String(describing: channelId))]) - } - } - - public static func parse_inputChannel(_ reader: BufferReader) -> InputChannel? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputChannel.inputChannel(channelId: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputChannelEmpty(_ reader: BufferReader) -> InputChannel? { - return Api.InputChannel.inputChannelEmpty - } - public static func parse_inputChannelFromMessage(_ reader: BufferReader) -> InputChannel? { - var _1: Api.InputPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputChannel.inputChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 757c3b0297..14d86d4ec5 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -1,3 +1,85 @@ +public extension Api { + indirect enum InputChannel: TypeConstructorDescription { + case inputChannel(channelId: Int64, accessHash: Int64) + case inputChannelEmpty + case inputChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputChannel(let channelId, let accessHash): + if boxed { + buffer.appendInt32(-212145112) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputChannelEmpty: + if boxed { + buffer.appendInt32(-292807034) + } + + break + case .inputChannelFromMessage(let peer, let msgId, let channelId): + if boxed { + buffer.appendInt32(1536380829) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputChannel(let channelId, let accessHash): + return ("inputChannel", [("channelId", String(describing: channelId)), ("accessHash", String(describing: accessHash))]) + case .inputChannelEmpty: + return ("inputChannelEmpty", []) + case .inputChannelFromMessage(let peer, let msgId, let channelId): + return ("inputChannelFromMessage", [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("channelId", String(describing: channelId))]) + } + } + + public static func parse_inputChannel(_ reader: BufferReader) -> InputChannel? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputChannel.inputChannel(channelId: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputChannelEmpty(_ reader: BufferReader) -> InputChannel? { + return Api.InputChannel.inputChannelEmpty + } + public static func parse_inputChannelFromMessage(_ reader: BufferReader) -> InputChannel? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputChannel.inputChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputChatPhoto: TypeConstructorDescription { case inputChatPhoto(id: Api.InputPhoto) @@ -922,129 +1004,3 @@ public extension Api { } } -public extension Api { - indirect enum InputGame: TypeConstructorDescription { - case inputGameID(id: Int64, accessHash: Int64) - case inputGameShortName(botId: Api.InputUser, shortName: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputGameID(let id, let accessHash): - if boxed { - buffer.appendInt32(53231223) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputGameShortName(let botId, let shortName): - if boxed { - buffer.appendInt32(-1020139510) - } - botId.serialize(buffer, true) - serializeString(shortName, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputGameID(let id, let accessHash): - return ("inputGameID", [("id", String(describing: id)), ("accessHash", String(describing: accessHash))]) - case .inputGameShortName(let botId, let shortName): - return ("inputGameShortName", [("botId", String(describing: botId)), ("shortName", String(describing: shortName))]) - } - } - - public static func parse_inputGameID(_ reader: BufferReader) -> InputGame? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputGame.inputGameID(id: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputGameShortName(_ reader: BufferReader) -> InputGame? { - var _1: Api.InputUser? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputUser - } - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputGame.inputGameShortName(botId: _1!, shortName: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum InputGeoPoint: TypeConstructorDescription { - case inputGeoPoint(flags: Int32, lat: Double, long: Double, accuracyRadius: Int32?) - case inputGeoPointEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputGeoPoint(let flags, let lat, let long, let accuracyRadius): - if boxed { - buffer.appendInt32(1210199983) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(accuracyRadius!, buffer: buffer, boxed: false)} - break - case .inputGeoPointEmpty: - if boxed { - buffer.appendInt32(-457104426) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputGeoPoint(let flags, let lat, let long, let accuracyRadius): - return ("inputGeoPoint", [("flags", String(describing: flags)), ("lat", String(describing: lat)), ("long", String(describing: long)), ("accuracyRadius", String(describing: accuracyRadius))]) - case .inputGeoPointEmpty: - return ("inputGeoPointEmpty", []) - } - } - - public static func parse_inputGeoPoint(_ reader: BufferReader) -> InputGeoPoint? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Double? - _2 = reader.readDouble() - var _3: Double? - _3 = reader.readDouble() - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputGeoPoint.inputGeoPoint(flags: _1!, lat: _2!, long: _3!, accuracyRadius: _4) - } - else { - return nil - } - } - public static func parse_inputGeoPointEmpty(_ reader: BufferReader) -> InputGeoPoint? { - return Api.InputGeoPoint.inputGeoPointEmpty - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api8.swift b/submodules/TelegramApi/Sources/Api8.swift index 82831233d0..82091170e6 100644 --- a/submodules/TelegramApi/Sources/Api8.swift +++ b/submodules/TelegramApi/Sources/Api8.swift @@ -1,3 +1,129 @@ +public extension Api { + indirect enum InputGame: TypeConstructorDescription { + case inputGameID(id: Int64, accessHash: Int64) + case inputGameShortName(botId: Api.InputUser, shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputGameID(let id, let accessHash): + if boxed { + buffer.appendInt32(53231223) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputGameShortName(let botId, let shortName): + if boxed { + buffer.appendInt32(-1020139510) + } + botId.serialize(buffer, true) + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputGameID(let id, let accessHash): + return ("inputGameID", [("id", String(describing: id)), ("accessHash", String(describing: accessHash))]) + case .inputGameShortName(let botId, let shortName): + return ("inputGameShortName", [("botId", String(describing: botId)), ("shortName", String(describing: shortName))]) + } + } + + public static func parse_inputGameID(_ reader: BufferReader) -> InputGame? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputGame.inputGameID(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputGameShortName(_ reader: BufferReader) -> InputGame? { + var _1: Api.InputUser? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputUser + } + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputGame.inputGameShortName(botId: _1!, shortName: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum InputGeoPoint: TypeConstructorDescription { + case inputGeoPoint(flags: Int32, lat: Double, long: Double, accuracyRadius: Int32?) + case inputGeoPointEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputGeoPoint(let flags, let lat, let long, let accuracyRadius): + if boxed { + buffer.appendInt32(1210199983) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(accuracyRadius!, buffer: buffer, boxed: false)} + break + case .inputGeoPointEmpty: + if boxed { + buffer.appendInt32(-457104426) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputGeoPoint(let flags, let lat, let long, let accuracyRadius): + return ("inputGeoPoint", [("flags", String(describing: flags)), ("lat", String(describing: lat)), ("long", String(describing: long)), ("accuracyRadius", String(describing: accuracyRadius))]) + case .inputGeoPointEmpty: + return ("inputGeoPointEmpty", []) + } + } + + public static func parse_inputGeoPoint(_ reader: BufferReader) -> InputGeoPoint? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Double? + _2 = reader.readDouble() + var _3: Double? + _3 = reader.readDouble() + var _4: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputGeoPoint.inputGeoPoint(flags: _1!, lat: _2!, long: _3!, accuracyRadius: _4) + } + else { + return nil + } + } + public static func parse_inputGeoPointEmpty(_ reader: BufferReader) -> InputGeoPoint? { + return Api.InputGeoPoint.inputGeoPointEmpty + } + + } +} public extension Api { enum InputGroupCall: TypeConstructorDescription { case inputGroupCall(id: Int64, accessHash: Int64) @@ -914,171 +1040,3 @@ public extension Api { } } -public extension Api { - indirect enum InputPeer: TypeConstructorDescription { - case inputPeerChannel(channelId: Int64, accessHash: Int64) - case inputPeerChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int64) - case inputPeerChat(chatId: Int64) - case inputPeerEmpty - case inputPeerSelf - case inputPeerUser(userId: Int64, accessHash: Int64) - case inputPeerUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPeerChannel(let channelId, let accessHash): - if boxed { - buffer.appendInt32(666680316) - } - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputPeerChannelFromMessage(let peer, let msgId, let channelId): - if boxed { - buffer.appendInt32(-1121318848) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - break - case .inputPeerChat(let chatId): - if boxed { - buffer.appendInt32(900291769) - } - serializeInt64(chatId, buffer: buffer, boxed: false) - break - case .inputPeerEmpty: - if boxed { - buffer.appendInt32(2134579434) - } - - break - case .inputPeerSelf: - if boxed { - buffer.appendInt32(2107670217) - } - - break - case .inputPeerUser(let userId, let accessHash): - if boxed { - buffer.appendInt32(-571955892) - } - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputPeerUserFromMessage(let peer, let msgId, let userId): - if boxed { - buffer.appendInt32(-1468331492) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputPeerChannel(let channelId, let accessHash): - return ("inputPeerChannel", [("channelId", String(describing: channelId)), ("accessHash", String(describing: accessHash))]) - case .inputPeerChannelFromMessage(let peer, let msgId, let channelId): - return ("inputPeerChannelFromMessage", [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("channelId", String(describing: channelId))]) - case .inputPeerChat(let chatId): - return ("inputPeerChat", [("chatId", String(describing: chatId))]) - case .inputPeerEmpty: - return ("inputPeerEmpty", []) - case .inputPeerSelf: - return ("inputPeerSelf", []) - case .inputPeerUser(let userId, let accessHash): - return ("inputPeerUser", [("userId", String(describing: userId)), ("accessHash", String(describing: accessHash))]) - case .inputPeerUserFromMessage(let peer, let msgId, let userId): - return ("inputPeerUserFromMessage", [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("userId", String(describing: userId))]) - } - } - - public static func parse_inputPeerChannel(_ reader: BufferReader) -> InputPeer? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPeer.inputPeerChannel(channelId: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputPeerChannelFromMessage(_ reader: BufferReader) -> InputPeer? { - var _1: Api.InputPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputPeer.inputPeerChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!) - } - else { - return nil - } - } - public static func parse_inputPeerChat(_ reader: BufferReader) -> InputPeer? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.InputPeer.inputPeerChat(chatId: _1!) - } - else { - return nil - } - } - public static func parse_inputPeerEmpty(_ reader: BufferReader) -> InputPeer? { - return Api.InputPeer.inputPeerEmpty - } - public static func parse_inputPeerSelf(_ reader: BufferReader) -> InputPeer? { - return Api.InputPeer.inputPeerSelf - } - public static func parse_inputPeerUser(_ reader: BufferReader) -> InputPeer? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPeer.inputPeerUser(userId: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputPeerUserFromMessage(_ reader: BufferReader) -> InputPeer? { - var _1: Api.InputPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputPeer.inputPeerUserFromMessage(peer: _1!, msgId: _2!, userId: _3!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api9.swift b/submodules/TelegramApi/Sources/Api9.swift index df49a053d9..dab267e25b 100644 --- a/submodules/TelegramApi/Sources/Api9.swift +++ b/submodules/TelegramApi/Sources/Api9.swift @@ -1,3 +1,171 @@ +public extension Api { + indirect enum InputPeer: TypeConstructorDescription { + case inputPeerChannel(channelId: Int64, accessHash: Int64) + case inputPeerChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int64) + case inputPeerChat(chatId: Int64) + case inputPeerEmpty + case inputPeerSelf + case inputPeerUser(userId: Int64, accessHash: Int64) + case inputPeerUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputPeerChannel(let channelId, let accessHash): + if boxed { + buffer.appendInt32(666680316) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputPeerChannelFromMessage(let peer, let msgId, let channelId): + if boxed { + buffer.appendInt32(-1121318848) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + break + case .inputPeerChat(let chatId): + if boxed { + buffer.appendInt32(900291769) + } + serializeInt64(chatId, buffer: buffer, boxed: false) + break + case .inputPeerEmpty: + if boxed { + buffer.appendInt32(2134579434) + } + + break + case .inputPeerSelf: + if boxed { + buffer.appendInt32(2107670217) + } + + break + case .inputPeerUser(let userId, let accessHash): + if boxed { + buffer.appendInt32(-571955892) + } + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputPeerUserFromMessage(let peer, let msgId, let userId): + if boxed { + buffer.appendInt32(-1468331492) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputPeerChannel(let channelId, let accessHash): + return ("inputPeerChannel", [("channelId", String(describing: channelId)), ("accessHash", String(describing: accessHash))]) + case .inputPeerChannelFromMessage(let peer, let msgId, let channelId): + return ("inputPeerChannelFromMessage", [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("channelId", String(describing: channelId))]) + case .inputPeerChat(let chatId): + return ("inputPeerChat", [("chatId", String(describing: chatId))]) + case .inputPeerEmpty: + return ("inputPeerEmpty", []) + case .inputPeerSelf: + return ("inputPeerSelf", []) + case .inputPeerUser(let userId, let accessHash): + return ("inputPeerUser", [("userId", String(describing: userId)), ("accessHash", String(describing: accessHash))]) + case .inputPeerUserFromMessage(let peer, let msgId, let userId): + return ("inputPeerUserFromMessage", [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("userId", String(describing: userId))]) + } + } + + public static func parse_inputPeerChannel(_ reader: BufferReader) -> InputPeer? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputPeer.inputPeerChannel(channelId: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputPeerChannelFromMessage(_ reader: BufferReader) -> InputPeer? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputPeer.inputPeerChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!) + } + else { + return nil + } + } + public static func parse_inputPeerChat(_ reader: BufferReader) -> InputPeer? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.InputPeer.inputPeerChat(chatId: _1!) + } + else { + return nil + } + } + public static func parse_inputPeerEmpty(_ reader: BufferReader) -> InputPeer? { + return Api.InputPeer.inputPeerEmpty + } + public static func parse_inputPeerSelf(_ reader: BufferReader) -> InputPeer? { + return Api.InputPeer.inputPeerSelf + } + public static func parse_inputPeerUser(_ reader: BufferReader) -> InputPeer? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputPeer.inputPeerUser(userId: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputPeerUserFromMessage(_ reader: BufferReader) -> InputPeer? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputPeer.inputPeerUserFromMessage(peer: _1!, msgId: _2!, userId: _3!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputPeerNotifySettings: TypeConstructorDescription { case inputPeerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, sound: Api.NotificationSound?) @@ -672,155 +840,3 @@ public extension Api { } } -public extension Api { - enum InputStickerSet: TypeConstructorDescription { - case inputStickerSetAnimatedEmoji - case inputStickerSetAnimatedEmojiAnimations - case inputStickerSetDice(emoticon: String) - case inputStickerSetEmojiDefaultStatuses - case inputStickerSetEmojiGenericAnimations - case inputStickerSetEmpty - case inputStickerSetID(id: Int64, accessHash: Int64) - case inputStickerSetPremiumGifts - case inputStickerSetShortName(shortName: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStickerSetAnimatedEmoji: - if boxed { - buffer.appendInt32(42402760) - } - - break - case .inputStickerSetAnimatedEmojiAnimations: - if boxed { - buffer.appendInt32(215889721) - } - - break - case .inputStickerSetDice(let emoticon): - if boxed { - buffer.appendInt32(-427863538) - } - serializeString(emoticon, buffer: buffer, boxed: false) - break - case .inputStickerSetEmojiDefaultStatuses: - if boxed { - buffer.appendInt32(701560302) - } - - break - case .inputStickerSetEmojiGenericAnimations: - if boxed { - buffer.appendInt32(80008398) - } - - break - case .inputStickerSetEmpty: - if boxed { - buffer.appendInt32(-4838507) - } - - break - case .inputStickerSetID(let id, let accessHash): - if boxed { - buffer.appendInt32(-1645763991) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputStickerSetPremiumGifts: - if boxed { - buffer.appendInt32(-930399486) - } - - break - case .inputStickerSetShortName(let shortName): - if boxed { - buffer.appendInt32(-2044933984) - } - serializeString(shortName, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputStickerSetAnimatedEmoji: - return ("inputStickerSetAnimatedEmoji", []) - case .inputStickerSetAnimatedEmojiAnimations: - return ("inputStickerSetAnimatedEmojiAnimations", []) - case .inputStickerSetDice(let emoticon): - return ("inputStickerSetDice", [("emoticon", String(describing: emoticon))]) - case .inputStickerSetEmojiDefaultStatuses: - return ("inputStickerSetEmojiDefaultStatuses", []) - case .inputStickerSetEmojiGenericAnimations: - return ("inputStickerSetEmojiGenericAnimations", []) - case .inputStickerSetEmpty: - return ("inputStickerSetEmpty", []) - case .inputStickerSetID(let id, let accessHash): - return ("inputStickerSetID", [("id", String(describing: id)), ("accessHash", String(describing: accessHash))]) - case .inputStickerSetPremiumGifts: - return ("inputStickerSetPremiumGifts", []) - case .inputStickerSetShortName(let shortName): - return ("inputStickerSetShortName", [("shortName", String(describing: shortName))]) - } - } - - public static func parse_inputStickerSetAnimatedEmoji(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetAnimatedEmoji - } - public static func parse_inputStickerSetAnimatedEmojiAnimations(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetAnimatedEmojiAnimations - } - public static func parse_inputStickerSetDice(_ reader: BufferReader) -> InputStickerSet? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.InputStickerSet.inputStickerSetDice(emoticon: _1!) - } - else { - return nil - } - } - public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses - } - public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations - } - public static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetEmpty - } - public static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputStickerSet.inputStickerSetID(id: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputStickerSetPremiumGifts(_ reader: BufferReader) -> InputStickerSet? { - return Api.InputStickerSet.inputStickerSetPremiumGifts - } - public static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.InputStickerSet.inputStickerSetShortName(shortName: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift index eb9be44c80..58dc7cdd94 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift @@ -124,6 +124,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { if (flags & Int32(1 << 29)) != 0 { channelFlags.insert(.requestToJoin) } + if (flags & Int32(1 << 30)) != 0 { + channelFlags.insert(.isForum) + } let restrictionInfo: PeerAccessRestrictionInfo? if let restrictionReason = restrictionReason { diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 7e2d61aead..5a35ab8c71 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -201,7 +201,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEditTitle, .messageActionTopicEditIcon: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) @@ -632,6 +632,10 @@ extension StoreMessage { if (flags & (1 << 26)) != 0 { storeFlags.insert(.CopyProtected) } + + if (flags & (1 << 27)) != 0 { + storeFlags.insert(.IsForumTopic) + } if (flags & (1 << 4)) != 0 || (flags & (1 << 13)) != 0 { var notificationFlags: NotificationInfoMessageAttributeFlags = [] @@ -723,6 +727,10 @@ extension StoreMessage { if (flags & (1 << 26)) != 0 { storeFlags.insert(.CopyProtected) } + + if (flags & (1 << 27)) != 0 { + storeFlags.insert(.IsForumTopic) + } self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media) } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 19a12ad360..cd63d4a567 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -5,88 +5,94 @@ import TelegramApi func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMediaAction? { switch action { - case let .messageActionChannelCreate(title): - return TelegramMediaAction(action: .groupCreated(title: title)) - case let .messageActionChannelMigrateFrom(title, chatId): - return TelegramMediaAction(action: .channelMigratedFromGroup(title: title, groupId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)))) - case let .messageActionChatAddUser(users): - return TelegramMediaAction(action: .addedMembers(peerIds: users.map({ PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }))) - case let .messageActionChatCreate(title, _): - return TelegramMediaAction(action: .groupCreated(title: title)) - case .messageActionChatDeletePhoto: - return TelegramMediaAction(action: .photoUpdated(image: nil)) - case let .messageActionChatDeleteUser(userId): - return TelegramMediaAction(action: .removedMembers(peerIds: [PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))])) - case let .messageActionChatEditPhoto(photo): - return TelegramMediaAction(action: .photoUpdated(image: telegramMediaImageFromApiPhoto(photo))) - case let .messageActionChatEditTitle(title): - return TelegramMediaAction(action: .titleUpdated(title: title)) - case let .messageActionChatJoinedByLink(inviterId): - return TelegramMediaAction(action: .joinedByLink(inviter: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId)))) - case let .messageActionChatMigrateTo(channelId): - return TelegramMediaAction(action: .groupMigratedToChannel(channelId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)))) - case .messageActionHistoryClear: - return TelegramMediaAction(action: .historyCleared) - case .messageActionPinMessage: - return TelegramMediaAction(action: .pinnedMessageUpdated) - case let .messageActionGameScore(gameId, score): - return TelegramMediaAction(action: .gameScore(gameId: gameId, score: score)) - case let .messageActionPhoneCall(flags, callId, reason, duration): - var discardReason: PhoneCallDiscardReason? - if let reason = reason { - discardReason = PhoneCallDiscardReason(apiReason: reason) - } - let isVideo = (flags & (1 << 2)) != 0 - return TelegramMediaAction(action: .phoneCall(callId: callId, discardReason: discardReason, duration: duration, isVideo: isVideo)) - case .messageActionEmpty: - return nil - case let .messageActionPaymentSent(flags, currency, totalAmount, invoiceSlug): - let isRecurringInit = (flags & (1 << 2)) != 0 - let isRecurringUsed = (flags & (1 << 3)) != 0 - return TelegramMediaAction(action: .paymentSent(currency: currency, totalAmount: totalAmount, invoiceSlug: invoiceSlug, isRecurringInit: isRecurringInit, isRecurringUsed: isRecurringUsed)) - case .messageActionPaymentSentMe: - return nil - case .messageActionScreenshotTaken: - return TelegramMediaAction(action: .historyScreenshot) - case let .messageActionCustomAction(message): - return TelegramMediaAction(action: .customText(text: message, entities: [])) - case let .messageActionBotAllowed(domain): - return TelegramMediaAction(action: .botDomainAccessGranted(domain: domain)) - case .messageActionSecureValuesSentMe: - return nil - case let .messageActionSecureValuesSent(types): - return TelegramMediaAction(action: .botSentSecureValues(types: types.map(SentSecureValueType.init))) - case .messageActionContactSignUp: - return TelegramMediaAction(action: .peerJoined) - case let .messageActionGeoProximityReached(fromId, toId, distance): - return TelegramMediaAction(action: .geoProximityReached(from: fromId.peerId, to: toId.peerId, distance: distance)) - case let .messageActionGroupCall(_, call, duration): - switch call { - case let .inputGroupCall(id, accessHash): - return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: nil, duration: duration)) - } - case let .messageActionInviteToGroupCall(call, userIds): - switch call { - case let .inputGroupCall(id, accessHash): - return TelegramMediaAction(action: .inviteToGroupPhoneCall(callId: id, accessHash: accessHash, peerIds: userIds.map { userId in - PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) - })) - } - case let .messageActionSetMessagesTTL(period): - return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period)) - case let .messageActionGroupCallScheduled(call, scheduleDate): - switch call { - case let .inputGroupCall(id, accessHash): - return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: scheduleDate, duration: nil)) - } - case let .messageActionSetChatTheme(emoji): - return TelegramMediaAction(action: .setChatTheme(emoji: emoji)) - case .messageActionChatJoinedByRequest: - return TelegramMediaAction(action: .joinedByRequest) - case let .messageActionWebViewDataSentMe(text, _), let .messageActionWebViewDataSent(text): - return TelegramMediaAction(action: .webViewData(text)) - case let .messageActionGiftPremium(currency, amount, months): - return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months)) + case let .messageActionChannelCreate(title): + return TelegramMediaAction(action: .groupCreated(title: title)) + case let .messageActionChannelMigrateFrom(title, chatId): + return TelegramMediaAction(action: .channelMigratedFromGroup(title: title, groupId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)))) + case let .messageActionChatAddUser(users): + return TelegramMediaAction(action: .addedMembers(peerIds: users.map({ PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }))) + case let .messageActionChatCreate(title, _): + return TelegramMediaAction(action: .groupCreated(title: title)) + case .messageActionChatDeletePhoto: + return TelegramMediaAction(action: .photoUpdated(image: nil)) + case let .messageActionChatDeleteUser(userId): + return TelegramMediaAction(action: .removedMembers(peerIds: [PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))])) + case let .messageActionChatEditPhoto(photo): + return TelegramMediaAction(action: .photoUpdated(image: telegramMediaImageFromApiPhoto(photo))) + case let .messageActionChatEditTitle(title): + return TelegramMediaAction(action: .titleUpdated(title: title)) + case let .messageActionChatJoinedByLink(inviterId): + return TelegramMediaAction(action: .joinedByLink(inviter: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(inviterId)))) + case let .messageActionChatMigrateTo(channelId): + return TelegramMediaAction(action: .groupMigratedToChannel(channelId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)))) + case .messageActionHistoryClear: + return TelegramMediaAction(action: .historyCleared) + case .messageActionPinMessage: + return TelegramMediaAction(action: .pinnedMessageUpdated) + case let .messageActionGameScore(gameId, score): + return TelegramMediaAction(action: .gameScore(gameId: gameId, score: score)) + case let .messageActionPhoneCall(flags, callId, reason, duration): + var discardReason: PhoneCallDiscardReason? + if let reason = reason { + discardReason = PhoneCallDiscardReason(apiReason: reason) + } + let isVideo = (flags & (1 << 2)) != 0 + return TelegramMediaAction(action: .phoneCall(callId: callId, discardReason: discardReason, duration: duration, isVideo: isVideo)) + case .messageActionEmpty: + return nil + case let .messageActionPaymentSent(flags, currency, totalAmount, invoiceSlug): + let isRecurringInit = (flags & (1 << 2)) != 0 + let isRecurringUsed = (flags & (1 << 3)) != 0 + return TelegramMediaAction(action: .paymentSent(currency: currency, totalAmount: totalAmount, invoiceSlug: invoiceSlug, isRecurringInit: isRecurringInit, isRecurringUsed: isRecurringUsed)) + case .messageActionPaymentSentMe: + return nil + case .messageActionScreenshotTaken: + return TelegramMediaAction(action: .historyScreenshot) + case let .messageActionCustomAction(message): + return TelegramMediaAction(action: .customText(text: message, entities: [])) + case let .messageActionBotAllowed(domain): + return TelegramMediaAction(action: .botDomainAccessGranted(domain: domain)) + case .messageActionSecureValuesSentMe: + return nil + case let .messageActionSecureValuesSent(types): + return TelegramMediaAction(action: .botSentSecureValues(types: types.map(SentSecureValueType.init))) + case .messageActionContactSignUp: + return TelegramMediaAction(action: .peerJoined) + case let .messageActionGeoProximityReached(fromId, toId, distance): + return TelegramMediaAction(action: .geoProximityReached(from: fromId.peerId, to: toId.peerId, distance: distance)) + case let .messageActionGroupCall(_, call, duration): + switch call { + case let .inputGroupCall(id, accessHash): + return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: nil, duration: duration)) + } + case let .messageActionInviteToGroupCall(call, userIds): + switch call { + case let .inputGroupCall(id, accessHash): + return TelegramMediaAction(action: .inviteToGroupPhoneCall(callId: id, accessHash: accessHash, peerIds: userIds.map { userId in + PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + })) + } + case let .messageActionSetMessagesTTL(period): + return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period)) + case let .messageActionGroupCallScheduled(call, scheduleDate): + switch call { + case let .inputGroupCall(id, accessHash): + return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, scheduleDate: scheduleDate, duration: nil)) + } + case let .messageActionSetChatTheme(emoji): + return TelegramMediaAction(action: .setChatTheme(emoji: emoji)) + case .messageActionChatJoinedByRequest: + return TelegramMediaAction(action: .joinedByRequest) + case let .messageActionWebViewDataSentMe(text, _), let .messageActionWebViewDataSent(text): + return TelegramMediaAction(action: .webViewData(text)) + case let .messageActionGiftPremium(currency, amount, months): + return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months)) + case let .messageActionTopicCreate(_, title, iconEmojiId): + return TelegramMediaAction(action: .topicCreated(title: title, iconFileId: iconEmojiId)) + case let .messageActionTopicEditTitle(title): + return TelegramMediaAction(action: .topicEditTitle(title: title)) + case let .messageActionTopicEditIcon(fileId): + return TelegramMediaAction(action: .topicEditIcon(fileId: fileId == 0 ? nil : fileId)) } } diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift new file mode 100644 index 0000000000..831b00adda --- /dev/null +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -0,0 +1,319 @@ +import Foundation +import SwiftSignalKit +import Postbox +import TelegramApi + +public final class EngineMessageHistoryThreads { + public final class Info: Equatable, Codable { + private enum CodingKeys: String, CodingKey { + case title + case icon + } + + public let title: String + public let icon: Int64? + + public init( + title: String, + icon: Int64? + ) { + self.title = title + self.icon = icon + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.title = try container.decode(String.self, forKey: .title) + self.icon = try container.decodeIfPresent(Int64.self, forKey: .icon) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.title, forKey: .title) + try container.encodeIfPresent(self.icon, forKey: .icon) + } + + public static func ==(lhs: Info, rhs: Info) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.icon != rhs.icon { + return false + } + return true + } + } +} + +func _internal_setChannelForumMode(account: Account, peerId: PeerId, isForum: Bool) -> Signal { + return account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> mapToSignal { inputChannel -> Signal in + guard let inputChannel = inputChannel else { + return .complete() + } + return account.network.request(Api.functions.channels.toggleForum(channel: inputChannel, enabled: isForum ? .boolTrue : .boolFalse)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .complete() + } + account.stateManager.addUpdates(result) + + return .complete() + } + } +} + +enum LoadMessageHistoryThreadsError { + case generic +} + +func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Signal { + let signal: Signal = account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> castError(LoadMessageHistoryThreadsError.self) + |> mapToSignal { inputChannel -> Signal in + guard let inputChannel = inputChannel else { + return .fail(.generic) + } + let signal: Signal = account.network.request(Api.functions.channels.getForumTopics( + flags: 0, + channel: inputChannel, + q: nil, + offsetDate: 0, + offsetId: 0, + offsetTopic: 0, + limit: 100 + )) + |> mapError { _ -> LoadMessageHistoryThreadsError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> Void in + switch result { + case let .forumTopics(flags, count, topics, messages, chats, users, pts): + var peers: [Peer] = [] + var peerPresences: [PeerId: Api.User] = [:] + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + peerPresences[telegramUser.id] = user + } + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + + updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) + + let _ = transaction.addMessages(messages.compactMap { message -> StoreMessage? in + return StoreMessage(apiMessage: message) + }, location: .Random) + + let _ = flags + let _ = count + let _ = topics + let _ = messages + let _ = chats + let _ = users + let _ = pts + + for topic in topics { + switch topic { + case let .forumTopic(_, id, _, title, iconEmojiId, _, _, _, _): + guard let info = CodableEntry(EngineMessageHistoryThreads.Info(title: title, icon: iconEmojiId)) else { + continue + } + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: Int64(id), info: info) + } + } + } + } + |> castError(LoadMessageHistoryThreadsError.self) + |> ignoreValues + } + return signal + } + + return signal +} + +public final class ForumChannelTopics { + private final class Impl { + private let queue: Queue + + private let account: Account + private let peerId: PeerId + + private let statePromise = Promise() + var state: Signal { + return self.statePromise.get() + } + + private let loadMoreDisposable = MetaDisposable() + private let createTopicDisposable = MetaDisposable() + + init(queue: Queue, account: Account, peerId: PeerId) { + self.queue = queue + self.account = account + self.peerId = peerId + + let _ = _internal_loadMessageHistoryThreads(account: self.account, peerId: peerId).start() + + let viewKey: PostboxViewKey = .messageHistoryThreadIndex(id: self.peerId) + self.statePromise.set(self.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> State in + guard let view = views.views[viewKey] as? MessageHistoryThreadIndexView else { + preconditionFailure() + } + return State(items: view.items.compactMap { item -> ForumChannelTopics.Item? in + guard let info = item.info.get(EngineMessageHistoryThreads.Info.self) else { + return nil + } + return ForumChannelTopics.Item( + id: item.id, + info: info, + index: item.index, + topMessage: item.topMessage.flatMap(EngineMessage.init) + ) + }) + }) + } + + deinit { + assert(self.queue.isCurrent()) + + self.loadMoreDisposable.dispose() + self.createTopicDisposable.dispose() + } + + func createTopic(title: String) { + let peerId = self.peerId + let account = self.account + let signal: Signal = self.account.postbox.transaction { transaction -> (Api.InputChannel?, Int64?) in + var fileId: Int64? = nil + + var filteredFiles: [TelegramMediaFile] = [] + for featuredEmojiPack in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) { + guard let featuredEmojiPack = featuredEmojiPack.contents.get(FeaturedStickerPackItem.self) else { + continue + } + for item in featuredEmojiPack.topItems { + for attribute in item.file.attributes { + switch attribute { + case .CustomEmoji: + filteredFiles.append(item.file) + default: + break + } + } + } + } + fileId = filteredFiles.randomElement()?.fileId.id + + return (transaction.getPeer(peerId).flatMap(apiInputChannel), fileId) + } + |> mapToSignal { inputChannel, fileId -> Signal in + guard let inputChannel = inputChannel else { + return .single(nil) + } + var flags: Int32 = 0 + if fileId != nil { + flags |= (1 << 3) + } + return account.network.request(Api.functions.channels.createForumTopic( + flags: flags, + channel: inputChannel, + title: title, + iconEmojiId: fileId, + randomId: Int64.random(in: Int64.min ..< Int64.max), + sendAs: nil + )) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .single(nil) + } + account.stateManager.addUpdates(result) + return .single(nil) + } + } + + self.createTopicDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let strongSelf = self else { + return + } + let _ = _internal_loadMessageHistoryThreads(account: strongSelf.account, peerId: strongSelf.peerId).start() + })) + } + } + + public struct Item: Equatable { + public var id: Int64 + public var info: EngineMessageHistoryThreads.Info + public var index: MessageIndex + public var topMessage: EngineMessage? + + init( + id: Int64, + info: EngineMessageHistoryThreads.Info, + index: MessageIndex, + topMessage: EngineMessage? + ) { + self.id = id + self.info = info + self.index = index + self.topMessage = topMessage + } + } + + public struct State: Equatable { + public var items: [Item] + + init(items: [Item]) { + self.items = items + } + } + + private let queue: Queue + private let impl: QueueLocalObject + + public var state: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.state.start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + + public init(account: Account, peerId: PeerId) { + let queue = Queue() + self.queue = queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue, account: account, peerId: peerId) + }) + } + + public func createTopic(title: String) { + self.impl.with { impl in + impl.createTopic(title: title) + } + } +} diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index ee119c6d76..ec2600126f 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 147 + return 148 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift index 31b084db2f..dad575fa0e 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift @@ -147,6 +147,7 @@ public struct TelegramChannelFlags: OptionSet { public static let copyProtectionEnabled = TelegramChannelFlags(rawValue: 1 << 8) public static let joinToSend = TelegramChannelFlags(rawValue: 1 << 9) public static let requestToJoin = TelegramChannelFlags(rawValue: 1 << 10) + public static let isForum = TelegramChannelFlags(rawValue: 1 << 11) } public final class TelegramChannel: Peer, Equatable { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 66606f6fb3..e7e371272f 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -52,226 +52,253 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case joinedByRequest case webViewData(String) case giftPremium(currency: String, amount: Int64, months: Int32) + case topicCreated(title: String, iconFileId: Int64?) + case topicEditTitle(title: String) + case topicEditIcon(fileId: Int64?) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) switch rawValue { - case 1: - self = .groupCreated(title: decoder.decodeStringForKey("title", orElse: "")) - case 2: - self = .addedMembers(peerIds: PeerId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("peerIds")!)) - case 3: - self = .removedMembers(peerIds: PeerId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("peerIds")!)) - case 4: - self = .photoUpdated(image: decoder.decodeObjectForKey("image") as? TelegramMediaImage) - case 5: - self = .titleUpdated(title: decoder.decodeStringForKey("title", orElse: "")) - case 6: - self = .pinnedMessageUpdated - case 7: - self = .joinedByLink(inviter: PeerId(decoder.decodeInt64ForKey("inviter", orElse: 0))) - case 8: - self = .channelMigratedFromGroup(title: decoder.decodeStringForKey("title", orElse: ""), groupId: PeerId(decoder.decodeInt64ForKey("groupId", orElse: 0))) - case 9: - self = .groupMigratedToChannel(channelId: PeerId(decoder.decodeInt64ForKey("channelId", orElse: 0))) - case 10: - self = .historyCleared - case 11: - self = .historyScreenshot - case 12: - self = .messageAutoremoveTimeoutUpdated(decoder.decodeInt32ForKey("t", orElse: 0)) - case 13: - self = .gameScore(gameId: decoder.decodeInt64ForKey("i", orElse: 0), score: decoder.decodeInt32ForKey("s", orElse: 0)) - case 14: - var discardReason: PhoneCallDiscardReason? - if let value = decoder.decodeOptionalInt32ForKey("dr") { - discardReason = PhoneCallDiscardReason(rawValue: value) - } - self = .phoneCall(callId: decoder.decodeInt64ForKey("i", orElse: 0), discardReason: discardReason, duration: decoder.decodeInt32ForKey("d", orElse: 0), isVideo: decoder.decodeInt32ForKey("vc", orElse: 0) != 0) - case 15: - self = .paymentSent(currency: decoder.decodeStringForKey("currency", orElse: ""), totalAmount: decoder.decodeInt64ForKey("ta", orElse: 0), invoiceSlug: decoder.decodeOptionalStringForKey("invoiceSlug"), isRecurringInit: decoder.decodeBoolForKey("isRecurringInit", orElse: false), isRecurringUsed: decoder.decodeBoolForKey("isRecurringUsed", orElse: false)) - case 16: - self = .customText(text: decoder.decodeStringForKey("text", orElse: ""), entities: decoder.decodeObjectArrayWithDecoderForKey("ent")) - case 17: - self = .botDomainAccessGranted(domain: decoder.decodeStringForKey("do", orElse: "")) - case 18: - self = .botSentSecureValues(types: decoder.decodeInt32ArrayForKey("ty").map { value -> SentSecureValueType in - return SentSecureValueType(rawValue: value) ?? .personalDetails - }) - case 19: - self = .peerJoined - case 20: - self = .phoneNumberRequest - case 21: - self = .geoProximityReached(from: PeerId(decoder.decodeInt64ForKey("fromId", orElse: 0)), to: PeerId(decoder.decodeInt64ForKey("toId", orElse: 0)), distance: (decoder.decodeInt32ForKey("dst", orElse: 0))) - case 22: - self = .groupPhoneCall(callId: decoder.decodeInt64ForKey("callId", orElse: 0), accessHash: decoder.decodeInt64ForKey("accessHash", orElse: 0), scheduleDate: decoder.decodeOptionalInt32ForKey("scheduleDate"), duration: decoder.decodeOptionalInt32ForKey("duration")) - case 23: - var peerIds: [PeerId] = [] - if let peerId = decoder.decodeOptionalInt64ForKey("peerId") { - peerIds.append(PeerId(peerId)) - } else { - peerIds = decoder.decodeInt64ArrayForKey("peerIds").map(PeerId.init) - } - self = .inviteToGroupPhoneCall(callId: decoder.decodeInt64ForKey("callId", orElse: 0), accessHash: decoder.decodeInt64ForKey("accessHash", orElse: 0), peerIds: peerIds) - case 24: - self = .setChatTheme(emoji: decoder.decodeStringForKey("emoji", orElse: "")) - case 25: - self = .joinedByRequest - case 26: - self = .webViewData(decoder.decodeStringForKey("t", orElse: "")) - case 27: - self = .giftPremium(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), months: decoder.decodeInt32ForKey("months", orElse: 0)) - default: - self = .unknown + case 1: + self = .groupCreated(title: decoder.decodeStringForKey("title", orElse: "")) + case 2: + self = .addedMembers(peerIds: PeerId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("peerIds")!)) + case 3: + self = .removedMembers(peerIds: PeerId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("peerIds")!)) + case 4: + self = .photoUpdated(image: decoder.decodeObjectForKey("image") as? TelegramMediaImage) + case 5: + self = .titleUpdated(title: decoder.decodeStringForKey("title", orElse: "")) + case 6: + self = .pinnedMessageUpdated + case 7: + self = .joinedByLink(inviter: PeerId(decoder.decodeInt64ForKey("inviter", orElse: 0))) + case 8: + self = .channelMigratedFromGroup(title: decoder.decodeStringForKey("title", orElse: ""), groupId: PeerId(decoder.decodeInt64ForKey("groupId", orElse: 0))) + case 9: + self = .groupMigratedToChannel(channelId: PeerId(decoder.decodeInt64ForKey("channelId", orElse: 0))) + case 10: + self = .historyCleared + case 11: + self = .historyScreenshot + case 12: + self = .messageAutoremoveTimeoutUpdated(decoder.decodeInt32ForKey("t", orElse: 0)) + case 13: + self = .gameScore(gameId: decoder.decodeInt64ForKey("i", orElse: 0), score: decoder.decodeInt32ForKey("s", orElse: 0)) + case 14: + var discardReason: PhoneCallDiscardReason? + if let value = decoder.decodeOptionalInt32ForKey("dr") { + discardReason = PhoneCallDiscardReason(rawValue: value) + } + self = .phoneCall(callId: decoder.decodeInt64ForKey("i", orElse: 0), discardReason: discardReason, duration: decoder.decodeInt32ForKey("d", orElse: 0), isVideo: decoder.decodeInt32ForKey("vc", orElse: 0) != 0) + case 15: + self = .paymentSent(currency: decoder.decodeStringForKey("currency", orElse: ""), totalAmount: decoder.decodeInt64ForKey("ta", orElse: 0), invoiceSlug: decoder.decodeOptionalStringForKey("invoiceSlug"), isRecurringInit: decoder.decodeBoolForKey("isRecurringInit", orElse: false), isRecurringUsed: decoder.decodeBoolForKey("isRecurringUsed", orElse: false)) + case 16: + self = .customText(text: decoder.decodeStringForKey("text", orElse: ""), entities: decoder.decodeObjectArrayWithDecoderForKey("ent")) + case 17: + self = .botDomainAccessGranted(domain: decoder.decodeStringForKey("do", orElse: "")) + case 18: + self = .botSentSecureValues(types: decoder.decodeInt32ArrayForKey("ty").map { value -> SentSecureValueType in + return SentSecureValueType(rawValue: value) ?? .personalDetails + }) + case 19: + self = .peerJoined + case 20: + self = .phoneNumberRequest + case 21: + self = .geoProximityReached(from: PeerId(decoder.decodeInt64ForKey("fromId", orElse: 0)), to: PeerId(decoder.decodeInt64ForKey("toId", orElse: 0)), distance: (decoder.decodeInt32ForKey("dst", orElse: 0))) + case 22: + self = .groupPhoneCall(callId: decoder.decodeInt64ForKey("callId", orElse: 0), accessHash: decoder.decodeInt64ForKey("accessHash", orElse: 0), scheduleDate: decoder.decodeOptionalInt32ForKey("scheduleDate"), duration: decoder.decodeOptionalInt32ForKey("duration")) + case 23: + var peerIds: [PeerId] = [] + if let peerId = decoder.decodeOptionalInt64ForKey("peerId") { + peerIds.append(PeerId(peerId)) + } else { + peerIds = decoder.decodeInt64ArrayForKey("peerIds").map(PeerId.init) + } + self = .inviteToGroupPhoneCall(callId: decoder.decodeInt64ForKey("callId", orElse: 0), accessHash: decoder.decodeInt64ForKey("accessHash", orElse: 0), peerIds: peerIds) + case 24: + self = .setChatTheme(emoji: decoder.decodeStringForKey("emoji", orElse: "")) + case 25: + self = .joinedByRequest + case 26: + self = .webViewData(decoder.decodeStringForKey("t", orElse: "")) + case 27: + self = .giftPremium(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), months: decoder.decodeInt32ForKey("months", orElse: 0)) + case 28: + self = .topicCreated(title: decoder.decodeStringForKey("t", orElse: ""), iconFileId: decoder.decodeOptionalInt64ForKey("fid")) + case 29: + self = .topicEditTitle(title: decoder.decodeStringForKey("t", orElse: "")) + case 30: + self = .topicEditIcon(fileId: decoder.decodeOptionalInt64ForKey("fid")) + default: + self = .unknown } } public func encode(_ encoder: PostboxEncoder) { switch self { - case .unknown: - break - case let .groupCreated(title): - encoder.encodeInt32(1, forKey: "_rawValue") - encoder.encodeString(title, forKey: "title") - case let .addedMembers(peerIds): - encoder.encodeInt32(2, forKey: "_rawValue") - let buffer = WriteBuffer() - PeerId.encodeArrayToBuffer(peerIds, buffer: buffer) - encoder.encodeBytes(buffer, forKey: "peerIds") - case let .removedMembers(peerIds): - encoder.encodeInt32(3, forKey: "_rawValue") - let buffer = WriteBuffer() - PeerId.encodeArrayToBuffer(peerIds, buffer: buffer) - encoder.encodeBytes(buffer, forKey: "peerIds") - case let .photoUpdated(image): - encoder.encodeInt32(4, forKey: "_rawValue") - if let image = image { - encoder.encodeObject(image, forKey: "image") - } - case let .titleUpdated(title): - encoder.encodeInt32(5, forKey: "_rawValue") - encoder.encodeString(title, forKey: "title") - case .pinnedMessageUpdated: - encoder.encodeInt32(6, forKey: "_rawValue") - case let .joinedByLink(inviter): - encoder.encodeInt32(7, forKey: "_rawValue") - encoder.encodeInt64(inviter.toInt64(), forKey: "inviter") - case let .channelMigratedFromGroup(title, groupId): - encoder.encodeInt32(8, forKey: "_rawValue") - encoder.encodeString(title, forKey: "title") - encoder.encodeInt64(groupId.toInt64(), forKey: "groupId") - case let .groupMigratedToChannel(channelId): - encoder.encodeInt32(9, forKey: "_rawValue") - encoder.encodeInt64(channelId.toInt64(), forKey: "channelId") - case .historyCleared: - encoder.encodeInt32(10, forKey: "_rawValue") - case .historyScreenshot: - encoder.encodeInt32(11, forKey: "_rawValue") - case let .messageAutoremoveTimeoutUpdated(timeout): - encoder.encodeInt32(12, forKey: "_rawValue") - encoder.encodeInt32(timeout, forKey: "t") - case let .gameScore(gameId, score): - encoder.encodeInt32(13, forKey: "_rawValue") - encoder.encodeInt64(gameId, forKey: "i") - encoder.encodeInt32(score, forKey: "s") - case let .paymentSent(currency, totalAmount, invoiceSlug, isRecurringInit, isRecurringUsed): - encoder.encodeInt32(15, forKey: "_rawValue") - encoder.encodeString(currency, forKey: "currency") - encoder.encodeInt64(totalAmount, forKey: "ta") - if let invoiceSlug = invoiceSlug { - encoder.encodeString(invoiceSlug, forKey: "invoiceSlug") - } else { - encoder.encodeNil(forKey: "invoiceSlug") - } - encoder.encodeBool(isRecurringInit, forKey: "isRecurringInit") - encoder.encodeBool(isRecurringUsed, forKey: "isRecurringUsed") - case let .phoneCall(callId, discardReason, duration, isVideo): - encoder.encodeInt32(14, forKey: "_rawValue") - encoder.encodeInt64(callId, forKey: "i") - if let discardReason = discardReason { - encoder.encodeInt32(discardReason.rawValue, forKey: "dr") - } else { - encoder.encodeNil(forKey: "dr") - } - if let duration = duration { - encoder.encodeInt32(duration, forKey: "d") - } else { - encoder.encodeNil(forKey: "d") - } - encoder.encodeInt32(isVideo ? 1 : 0, forKey: "vc") - case let .customText(text, entities): - encoder.encodeInt32(16, forKey: "_rawValue") - encoder.encodeString(text, forKey: "text") - encoder.encodeObjectArray(entities, forKey: "ent") - case let .botDomainAccessGranted(domain): - encoder.encodeInt32(17, forKey: "_rawValue") - encoder.encodeString(domain, forKey: "do") - case let .botSentSecureValues(types): - encoder.encodeInt32(18, forKey: "_rawValue") - encoder.encodeInt32Array(types.map { $0.rawValue }, forKey: "ty") - case .peerJoined: - encoder.encodeInt32(19, forKey: "_rawValue") - case .phoneNumberRequest: - encoder.encodeInt32(20, forKey: "_rawValue") - case let .geoProximityReached(from, to, distance): - encoder.encodeInt32(21, forKey: "_rawValue") - encoder.encodeInt64(from.toInt64(), forKey: "fromId") - encoder.encodeInt64(to.toInt64(), forKey: "toId") - encoder.encodeInt32(distance, forKey: "dst") - case let .groupPhoneCall(callId, accessHash, scheduleDate, duration): - encoder.encodeInt32(22, forKey: "_rawValue") - encoder.encodeInt64(callId, forKey: "callId") - encoder.encodeInt64(accessHash, forKey: "accessHash") - if let scheduleDate = scheduleDate { - encoder.encodeInt32(scheduleDate, forKey: "scheduleDate") - } else { - encoder.encodeNil(forKey: "scheduleDate") - } - if let duration = duration { - encoder.encodeInt32(duration, forKey: "duration") - } else { - encoder.encodeNil(forKey: "duration") - } - case let .inviteToGroupPhoneCall(callId, accessHash, peerIds): - encoder.encodeInt32(23, forKey: "_rawValue") - encoder.encodeInt64(callId, forKey: "callId") - encoder.encodeInt64(accessHash, forKey: "accessHash") - encoder.encodeInt64Array(peerIds.map { $0.toInt64() }, forKey: "peerIds") - case let .setChatTheme(emoji): - encoder.encodeInt32(24, forKey: "_rawValue") - encoder.encodeString(emoji, forKey: "emoji") - case .joinedByRequest: - encoder.encodeInt32(25, forKey: "_rawValue") - case let .webViewData(text): - encoder.encodeInt32(26, forKey: "_rawValue") - encoder.encodeString(text, forKey: "t") - case let .giftPremium(currency, amount, months): - encoder.encodeInt32(27, forKey: "_rawValue") - encoder.encodeString(currency, forKey: "currency") - encoder.encodeInt64(amount, forKey: "amount") - encoder.encodeInt32(months, forKey: "months") + case .unknown: + break + case let .groupCreated(title): + encoder.encodeInt32(1, forKey: "_rawValue") + encoder.encodeString(title, forKey: "title") + case let .addedMembers(peerIds): + encoder.encodeInt32(2, forKey: "_rawValue") + let buffer = WriteBuffer() + PeerId.encodeArrayToBuffer(peerIds, buffer: buffer) + encoder.encodeBytes(buffer, forKey: "peerIds") + case let .removedMembers(peerIds): + encoder.encodeInt32(3, forKey: "_rawValue") + let buffer = WriteBuffer() + PeerId.encodeArrayToBuffer(peerIds, buffer: buffer) + encoder.encodeBytes(buffer, forKey: "peerIds") + case let .photoUpdated(image): + encoder.encodeInt32(4, forKey: "_rawValue") + if let image = image { + encoder.encodeObject(image, forKey: "image") + } + case let .titleUpdated(title): + encoder.encodeInt32(5, forKey: "_rawValue") + encoder.encodeString(title, forKey: "title") + case .pinnedMessageUpdated: + encoder.encodeInt32(6, forKey: "_rawValue") + case let .joinedByLink(inviter): + encoder.encodeInt32(7, forKey: "_rawValue") + encoder.encodeInt64(inviter.toInt64(), forKey: "inviter") + case let .channelMigratedFromGroup(title, groupId): + encoder.encodeInt32(8, forKey: "_rawValue") + encoder.encodeString(title, forKey: "title") + encoder.encodeInt64(groupId.toInt64(), forKey: "groupId") + case let .groupMigratedToChannel(channelId): + encoder.encodeInt32(9, forKey: "_rawValue") + encoder.encodeInt64(channelId.toInt64(), forKey: "channelId") + case .historyCleared: + encoder.encodeInt32(10, forKey: "_rawValue") + case .historyScreenshot: + encoder.encodeInt32(11, forKey: "_rawValue") + case let .messageAutoremoveTimeoutUpdated(timeout): + encoder.encodeInt32(12, forKey: "_rawValue") + encoder.encodeInt32(timeout, forKey: "t") + case let .gameScore(gameId, score): + encoder.encodeInt32(13, forKey: "_rawValue") + encoder.encodeInt64(gameId, forKey: "i") + encoder.encodeInt32(score, forKey: "s") + case let .paymentSent(currency, totalAmount, invoiceSlug, isRecurringInit, isRecurringUsed): + encoder.encodeInt32(15, forKey: "_rawValue") + encoder.encodeString(currency, forKey: "currency") + encoder.encodeInt64(totalAmount, forKey: "ta") + if let invoiceSlug = invoiceSlug { + encoder.encodeString(invoiceSlug, forKey: "invoiceSlug") + } else { + encoder.encodeNil(forKey: "invoiceSlug") + } + encoder.encodeBool(isRecurringInit, forKey: "isRecurringInit") + encoder.encodeBool(isRecurringUsed, forKey: "isRecurringUsed") + case let .phoneCall(callId, discardReason, duration, isVideo): + encoder.encodeInt32(14, forKey: "_rawValue") + encoder.encodeInt64(callId, forKey: "i") + if let discardReason = discardReason { + encoder.encodeInt32(discardReason.rawValue, forKey: "dr") + } else { + encoder.encodeNil(forKey: "dr") + } + if let duration = duration { + encoder.encodeInt32(duration, forKey: "d") + } else { + encoder.encodeNil(forKey: "d") + } + encoder.encodeInt32(isVideo ? 1 : 0, forKey: "vc") + case let .customText(text, entities): + encoder.encodeInt32(16, forKey: "_rawValue") + encoder.encodeString(text, forKey: "text") + encoder.encodeObjectArray(entities, forKey: "ent") + case let .botDomainAccessGranted(domain): + encoder.encodeInt32(17, forKey: "_rawValue") + encoder.encodeString(domain, forKey: "do") + case let .botSentSecureValues(types): + encoder.encodeInt32(18, forKey: "_rawValue") + encoder.encodeInt32Array(types.map { $0.rawValue }, forKey: "ty") + case .peerJoined: + encoder.encodeInt32(19, forKey: "_rawValue") + case .phoneNumberRequest: + encoder.encodeInt32(20, forKey: "_rawValue") + case let .geoProximityReached(from, to, distance): + encoder.encodeInt32(21, forKey: "_rawValue") + encoder.encodeInt64(from.toInt64(), forKey: "fromId") + encoder.encodeInt64(to.toInt64(), forKey: "toId") + encoder.encodeInt32(distance, forKey: "dst") + case let .groupPhoneCall(callId, accessHash, scheduleDate, duration): + encoder.encodeInt32(22, forKey: "_rawValue") + encoder.encodeInt64(callId, forKey: "callId") + encoder.encodeInt64(accessHash, forKey: "accessHash") + if let scheduleDate = scheduleDate { + encoder.encodeInt32(scheduleDate, forKey: "scheduleDate") + } else { + encoder.encodeNil(forKey: "scheduleDate") + } + if let duration = duration { + encoder.encodeInt32(duration, forKey: "duration") + } else { + encoder.encodeNil(forKey: "duration") + } + case let .inviteToGroupPhoneCall(callId, accessHash, peerIds): + encoder.encodeInt32(23, forKey: "_rawValue") + encoder.encodeInt64(callId, forKey: "callId") + encoder.encodeInt64(accessHash, forKey: "accessHash") + encoder.encodeInt64Array(peerIds.map { $0.toInt64() }, forKey: "peerIds") + case let .setChatTheme(emoji): + encoder.encodeInt32(24, forKey: "_rawValue") + encoder.encodeString(emoji, forKey: "emoji") + case .joinedByRequest: + encoder.encodeInt32(25, forKey: "_rawValue") + case let .webViewData(text): + encoder.encodeInt32(26, forKey: "_rawValue") + encoder.encodeString(text, forKey: "t") + case let .giftPremium(currency, amount, months): + encoder.encodeInt32(27, forKey: "_rawValue") + encoder.encodeString(currency, forKey: "currency") + encoder.encodeInt64(amount, forKey: "amount") + encoder.encodeInt32(months, forKey: "months") + case let .topicCreated(title, iconFileId): + encoder.encodeInt32(28, forKey: "_rawValue") + encoder.encodeString(title, forKey: "t") + if let fileId = iconFileId { + encoder.encodeInt64(fileId, forKey: "fid") + } else { + encoder.encodeNil(forKey: "fid") + } + case let .topicEditTitle(title): + encoder.encodeInt32(29, forKey: "_rawValue") + encoder.encodeString(title, forKey: "t") + case let .topicEditIcon(fileId): + encoder.encodeInt32(30, forKey: "_rawValue") + if let fileId = fileId { + encoder.encodeInt64(fileId, forKey: "fid") + } else { + encoder.encodeNil(forKey: "fid") + } } } public var peerIds: [PeerId] { switch self { - case let .addedMembers(peerIds): - return peerIds - case let .removedMembers(peerIds): - return peerIds - case let .joinedByLink(inviter): - return [inviter] - case let .channelMigratedFromGroup(_, groupId): - return [groupId] - case let .groupMigratedToChannel(channelId): - return [channelId] - case let .geoProximityReached(from, to, _): - return [from, to] - case let .inviteToGroupPhoneCall(_, _, peerIds): - return peerIds - default: - return [] + case let .addedMembers(peerIds): + return peerIds + case let .removedMembers(peerIds): + return peerIds + case let .joinedByLink(inviter): + return [inviter] + case let .channelMigratedFromGroup(_, groupId): + return [groupId] + case let .groupMigratedToChannel(channelId): + return [channelId] + case let .geoProximityReached(from, to, _): + return [from, to] + case let .inviteToGroupPhoneCall(_, _, peerIds): + return peerIds + default: + return [] } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift index 299988aac2..e8e472cb39 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift @@ -243,7 +243,7 @@ public extension TelegramEngine.EngineData.Item { guard let view = view as? ChatListIndexView else { preconditionFailure() } - return view.chatListIndex + return view.chatListIndex.flatMap(EngineChatList.Item.Index.chatList) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index ce77b1e1d0..d672e690ef 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -1,6 +1,6 @@ import Postbox -public final class EngineChatList { +public final class EngineChatList: Equatable { public enum Group { case root case archive @@ -17,7 +17,7 @@ public final class EngineChatList { case earlier(than: EngineChatList.Item.Index?) } - public struct Draft { + public struct Draft: Equatable { public var text: String public var entities: [MessageTextEntity] @@ -27,14 +27,52 @@ public final class EngineChatList { } } - public final class Item { - public typealias Index = ChatListIndex + public final class Item: Equatable { + public enum Id: Hashable { + case chatList(EnginePeer.Id) + case forum(Int64) + } + + public enum Index: Equatable, Comparable { + public typealias ChatList = ChatListIndex + + case chatList(ChatListIndex) + case forum(timestamp: Int32, threadId: Int64, namespace: EngineMessage.Id.Namespace, id: EngineMessage.Id.Id) + + public static func <(lhs: Index, rhs: Index) -> Bool { + switch lhs { + case let .chatList(lhsIndex): + if case let .chatList(rhsIndex) = rhs { + return lhsIndex < rhsIndex + } else { + return true + } + case let .forum(lhsTimestamp, lhsThreadId, lhsNamespace, lhsId): + if case let .forum(rhsTimestamp, rhsThreadId, rhsNamespace, rhsId) = rhs { + if lhsTimestamp != rhsTimestamp { + return lhsTimestamp < rhsTimestamp + } + if lhsThreadId != rhsThreadId { + return lhsThreadId < rhsThreadId + } + if lhsNamespace != rhsNamespace { + return lhsNamespace < rhsNamespace + } + return lhsId < rhsId + } else { + return false + } + } + } + } + public let id: Id public let index: Index public let messages: [EngineMessage] public let readCounters: EnginePeerReadCounters? public let isMuted: Bool public let draft: Draft? + public let threadInfo: EngineMessageHistoryThreads.Info? public let renderedPeer: EngineRenderedPeer public let presence: EnginePeer.Presence? public let hasUnseenMentions: Bool @@ -43,11 +81,13 @@ public final class EngineChatList { public let isContact: Bool public init( + id: Id, index: Index, messages: [EngineMessage], readCounters: EnginePeerReadCounters?, isMuted: Bool, draft: Draft?, + threadInfo: EngineMessageHistoryThreads.Info?, renderedPeer: EngineRenderedPeer, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, @@ -55,11 +95,13 @@ public final class EngineChatList { hasFailed: Bool, isContact: Bool ) { + self.id = id self.index = index self.messages = messages self.readCounters = readCounters self.isMuted = isMuted self.draft = draft + self.threadInfo = threadInfo self.renderedPeer = renderedPeer self.presence = presence self.hasUnseenMentions = hasUnseenMentions @@ -67,6 +109,49 @@ public final class EngineChatList { self.hasFailed = hasFailed self.isContact = isContact } + + public static func ==(lhs: Item, rhs: Item) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.index != rhs.index { + return false + } + if lhs.messages != rhs.messages { + return false + } + if lhs.readCounters != rhs.readCounters { + return false + } + if lhs.isMuted != rhs.isMuted { + return false + } + if lhs.draft != rhs.draft { + return false + } + if lhs.threadInfo != rhs.threadInfo { + return false + } + if lhs.renderedPeer != rhs.renderedPeer { + return false + } + if lhs.presence != rhs.presence { + return false + } + if lhs.hasUnseenMentions != rhs.hasUnseenMentions { + return false + } + if lhs.hasUnseenReactions != rhs.hasUnseenReactions { + return false + } + if lhs.hasFailed != rhs.hasFailed { + return false + } + if lhs.isContact != rhs.isContact { + return false + } + return true + } } public final class GroupItem: Equatable { @@ -127,9 +212,9 @@ public final class EngineChatList { } } - public final class AdditionalItem { - public final class PromoInfo { - public enum Content { + public final class AdditionalItem: Equatable { + public final class PromoInfo: Equatable { + public enum Content: Equatable { case proxy case psa(type: String, message: String?) } @@ -139,6 +224,14 @@ public final class EngineChatList { public init(content: Content) { self.content = content } + + public static func ==(lhs: PromoInfo, rhs: PromoInfo) -> Bool { + if lhs.content != rhs.content { + return false + } + + return true + } } public let item: Item @@ -148,6 +241,17 @@ public final class EngineChatList { self.item = item self.promoInfo = promoInfo } + + public static func ==(lhs: AdditionalItem, rhs: AdditionalItem) -> Bool { + if lhs.item != rhs.item { + return false + } + if lhs.promoInfo != rhs.promoInfo { + return false + } + + return true + } } public let items: [Item] @@ -157,7 +261,7 @@ public final class EngineChatList { public let hasLater: Bool public let isLoading: Bool - init( + public init( items: [Item], groupItems: [GroupItem], additionalItems: [AdditionalItem], @@ -172,6 +276,29 @@ public final class EngineChatList { self.hasLater = hasLater self.isLoading = isLoading } + + public static func ==(lhs: EngineChatList, rhs: EngineChatList) -> Bool { + if lhs.items != rhs.items { + return false + } + if lhs.groupItems != rhs.groupItems { + return false + } + if lhs.additionalItems != rhs.additionalItems { + return false + } + if lhs.hasEarlier != rhs.hasEarlier { + return false + } + if lhs.hasLater != rhs.hasLater { + return false + } + if lhs.isLoading != rhs.isLoading { + return false + } + + return true + } } public extension EngineChatList.Group { @@ -199,17 +326,23 @@ public extension EngineChatList.RelativePosition { init(_ position: ChatListRelativePosition) { switch position { case let .earlier(than): - self = .earlier(than: than) + self = .earlier(than: than.flatMap(EngineChatList.Item.Index.chatList)) case let .later(than): - self = .later(than: than) + self = .later(than: than.flatMap(EngineChatList.Item.Index.chatList)) } } - func _asPosition() -> ChatListRelativePosition { + func _asPosition() -> ChatListRelativePosition? { switch self { case let .earlier(than): + guard case let .chatList(than) = than else { + return nil + } return .earlier(than: than) case let .later(than): + guard case let .chatList(than) = than else { + return nil + } return .later(than: than) } } @@ -245,11 +378,13 @@ extension EngineChatList.Item { } self.init( - index: index, + id: .chatList(index.messageIndex.id.peerId), + index: .chatList(index), messages: messages.map(EngineMessage.init), readCounters: readState.flatMap(EnginePeerReadCounters.init), isMuted: isRemovedFromTotalUnreadCount, draft: draft, + threadInfo: nil, renderedPeer: EngineRenderedPeer(renderedPeer), presence: presence.flatMap(EnginePeer.Presence.init), hasUnseenMentions: hasUnseenMentions, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift index 2d62873e3b..fa8be0fb1a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift @@ -1,6 +1,6 @@ import Postbox -public final class EngineMessage { +public final class EngineMessage: Equatable { public typealias Id = MessageId public typealias Index = MessageIndex public typealias Tags = MessageTags @@ -148,4 +148,50 @@ public final class EngineMessage { public func _asMessage() -> Message { return self.impl } + + public static func ==(lhs: EngineMessage, rhs: EngineMessage) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.globallyUniqueId != rhs.globallyUniqueId { + return false + } + if lhs.groupingKey != rhs.groupingKey { + return false + } + if lhs.groupInfo != rhs.groupInfo { + return false + } + if lhs.threadId != rhs.threadId { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + if lhs.flags != rhs.flags { + return false + } + if lhs.tags != rhs.tags { + return false + } + if lhs.globalTags != rhs.globalTags { + return false + } + if lhs.localTags != rhs.localTags { + return false + } + if lhs.forwardInfo != rhs.forwardInfo { + return false + } + if lhs.author != rhs.author { + return false + } + if lhs.text != rhs.text { + return false + } + if !areMediaArraysEqual(lhs.media, rhs.media) { + return false + } + return true + } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index f1fd558bfa..e3cfc381d5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -440,8 +440,11 @@ public extension TelegramEngine { } public func getRelativeUnreadChatListIndex(filtered: Bool, position: EngineChatList.RelativePosition, groupId: EngineChatList.Group) -> Signal { + guard let position = position._asPosition() else { + return .single(nil) + } return self.account.postbox.transaction { transaction -> EngineChatList.Item.Index? in - return transaction.getRelativeUnreadChatListIndex(filtered: filtered, position: position._asPosition(), groupId: groupId._asGroup()) + return transaction.getRelativeUnreadChatListIndex(filtered: filtered, position: position, groupId: groupId._asGroup()).flatMap(EngineChatList.Item.Index.chatList) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelCreation.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelCreation.swift index dc42fe8395..44238541e4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelCreation.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelCreation.swift @@ -61,6 +61,17 @@ private func createChannel(account: Account, title: String, description: String? } |> castError(CreateChannelError.self) |> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic)) + |> mapToSignal { peerId -> Signal in + if title.contains("*forum") { + return _internal_setChannelForumMode(account: account, peerId: peerId, isForum: true) + |> castError(CreateChannelError.self) + |> map { _ -> PeerId in + } + |> then(.single(peerId)) + } else { + return .single(peerId) + } + } } else { return .fail(.generic) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index f06c780fc8..3b8796419f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -776,6 +776,10 @@ public extension TelegramEngine { } |> ignoreValues } + + public func setChannelForumMode(id: EnginePeer.Id, isForum: Bool) -> Signal { + return _internal_setChannelForumMode(account: self.account, peerId: id, isForum: isForum) + } } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index b1f8c7a495..534b2a7727 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -679,6 +679,16 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributes[1] = boldAttributes attributedString = addAttributesToStringWithRanges(strings.Notification_PremiumGift_Sent(authorName, price)._tuple, body: bodyAttributes, argumentAttributes: attributes) } + case .topicCreated: + //TODO:localize + attributedString = NSAttributedString(string: "Topic created", font: titleFont, textColor: primaryTextColor) + case let .topicEditTitle(title): + //TODO:localize + attributedString = NSAttributedString(string: "Topic renamed to \"\(title)\"", font: titleFont, textColor: primaryTextColor) + case let .topicEditIcon(fileId): + let _ = fileId + //TODO:localize + attributedString = NSAttributedString(string: "Topic icon changed", font: titleFont, textColor: primaryTextColor) case .unknown: attributedString = nil } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 52fe5cee57..fbf8af2f5a 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -297,6 +297,7 @@ swift_library( "//submodules/Media/LocalAudioTranscription:LocalAudioTranscription", "//submodules/Components/PagerComponent:PagerComponent", "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", + "//submodules/TelegramUI/Components/ForumTopicListScreen:ForumTopicListScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Components/ForumTopicListScreen/BUILD b/submodules/TelegramUI/Components/ForumTopicListScreen/BUILD new file mode 100644 index 0000000000..120b7b7d71 --- /dev/null +++ b/submodules/TelegramUI/Components/ForumTopicListScreen/BUILD @@ -0,0 +1,33 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ForumTopicListScreen", + module_name = "ForumTopicListScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/ComponentFlow:ComponentFlow", + "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", + "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", + "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent:MultilineTextComponent", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/AppBundle:AppBundle", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/PresentationDataUtils:PresentationDataUtils", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ForumTopicListScreen/Sources/ForumTopicListScreen.swift b/submodules/TelegramUI/Components/ForumTopicListScreen/Sources/ForumTopicListScreen.swift new file mode 100644 index 0000000000..31a6d4066e --- /dev/null +++ b/submodules/TelegramUI/Components/ForumTopicListScreen/Sources/ForumTopicListScreen.swift @@ -0,0 +1,495 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import SwiftSignalKit +import AnimationCache +import MultiAnimationRenderer +import ComponentDisplayAdapters +import TelegramPresentationData +import AccountContext +import Postbox +import TelegramCore + +private final class ForumTopicListItemComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let item: ForumChannelTopics.Item + let action: () -> Void + + init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + item: ForumChannelTopics.Item, + action: @escaping () -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.item = item + self.action = action + } + + static func ==(lhs: ForumTopicListItemComponent, rhs: ForumTopicListItemComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.item != rhs.item { + return false + } + return true + } + + final class View: HighlightTrackingButton { + private var highlightedBackgroundLayer: SimpleLayer? + private let title: ComponentView + + private var component: ForumTopicListItemComponent? + + override init(frame: CGRect) { + self.title = ComponentView() + + super.init(frame: frame) + + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + + self.highligthedChanged = { [weak self] highlighted in + if let self, let component = self.component { + if highlighted { + if let superview = self.superview { + superview.bringSubviewToFront(self) + } + let highlightedBackgroundLayer: SimpleLayer + if let current = self.highlightedBackgroundLayer { + highlightedBackgroundLayer = current + } else { + highlightedBackgroundLayer = SimpleLayer() + self.highlightedBackgroundLayer = highlightedBackgroundLayer + highlightedBackgroundLayer.backgroundColor = component.theme.list.itemHighlightedBackgroundColor.cgColor + highlightedBackgroundLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: self.bounds.width, height: self.bounds.height + UIScreenPixel)) + self.layer.insertSublayer(highlightedBackgroundLayer, at: 0) + } + highlightedBackgroundLayer.removeAllAnimations() + highlightedBackgroundLayer.opacity = 1.0 + } else { + if let highlightedBackgroundLayer = self.highlightedBackgroundLayer { + self.highlightedBackgroundLayer = nil + highlightedBackgroundLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak highlightedBackgroundLayer] _ in + highlightedBackgroundLayer?.removeFromSuperlayer() + }) + } + } + } + } + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + @objc private func pressed() { + self.component?.action() + } + + func update(component: ForumTopicListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(Text( + text: component.item.info.title, + font: Font.regular(17.0), + color: component.theme.list.itemPrimaryTextColor + )), + environment: {}, + containerSize: availableSize + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + titleView.isUserInteractionEnabled = false + } + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: 11.0, y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ForumTopicListComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let items: [ForumChannelTopics.Item] + let navigationHeight: CGFloat + let action: (ForumChannelTopics.Item) -> Void + + init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + items: [ForumChannelTopics.Item], + navigationHeight: CGFloat, + action: @escaping (ForumChannelTopics.Item) -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.items = items + self.navigationHeight = navigationHeight + self.action = action + } + + static func ==(lhs: ForumTopicListComponent, rhs: ForumTopicListComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.items != rhs.items { + return false + } + if lhs.navigationHeight != rhs.navigationHeight { + return false + } + return true + } + + final class View: UIView, UIScrollViewDelegate { + private struct ItemLayout { + let containerSize: CGSize + let itemHeight: CGFloat + let contentSize: CGSize + let itemsInsets: UIEdgeInsets + + init(containerSize: CGSize, navigationHeight: CGFloat, itemCount: Int) { + self.itemHeight = 44.0 + self.containerSize = containerSize + self.itemsInsets = UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: 0.0, right: 0.0) + self.contentSize = CGSize(width: containerSize.width, height: self.itemsInsets.top + self.itemsInsets.bottom + CGFloat(itemCount) * self.itemHeight) + } + + func frame(at index: Int) -> CGRect { + return CGRect(origin: CGPoint(x: 0.0, y: self.itemsInsets.top + CGFloat(index) * self.itemHeight), size: CGSize(width: self.containerSize.width, height: self.itemHeight)) + } + } + + private final class ItemView { + let host: ComponentView + let separatorLayer: SimpleLayer + + init() { + self.host = ComponentView() + self.separatorLayer = SimpleLayer() + } + } + + private let scrollView: UIScrollView + + private var component: ForumTopicListComponent? + private var itemLayout: ItemLayout? + + private var ignoreScrolling: Bool = false + private var visibleItemViews: [Int64: ItemView] = [:] + + override init(frame: CGRect) { + self.scrollView = UIScrollView() + + super.init(frame: frame) + + self.scrollView.layer.anchorPoint = CGPoint() + self.scrollView.delaysContentTouches = true + self.scrollView.clipsToBounds = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = true + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = true + self.scrollView.canCancelContentTouches = true + + self.addSubview(self.scrollView) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateVisibleItems(transition: .immediate, synchronous: false) + } + } + + private func updateVisibleItems(transition: Transition, synchronous: Bool) { + guard let component = self.component, let itemLayout = self.itemLayout else { + return + } + + var validIds = Set() + let visibleBounds = self.scrollView.bounds + for index in 0 ..< component.items.count { + let itemFrame = itemLayout.frame(at: index) + if !visibleBounds.intersects(itemFrame) { + continue + } + + let item = component.items[index] + validIds.insert(item.id) + + let itemView: ItemView + var itemTransition = transition + if let current = self.visibleItemViews[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ItemView() + self.visibleItemViews[item.id] = itemView + } + + let id = item.id + let _ = itemView.host.update( + transition: itemTransition, + component: AnyComponent(ForumTopicListItemComponent( + context: component.context, + theme: component.theme, + strings: component.strings, + item: item, + action: { [weak self] in + guard let strongSelf = self, let component = strongSelf.component else { + return + } + for item in component.items { + if item.id == id { + component.action(item) + break + } + } + } + )), + environment: {}, + containerSize: itemFrame.size + ) + if let itemComponentView = itemView.host.view { + if itemComponentView.superview == nil { + self.scrollView.addSubview(itemComponentView) + self.scrollView.layer.addSublayer(itemView.separatorLayer) + } + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) + + let separatorInset: CGFloat + if index == component.items.count - 1 { + separatorInset = 0.0 + } else { + separatorInset = 16.0 + } + itemView.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor + itemTransition.setFrame(layer: itemView.separatorLayer, frame: CGRect(origin: CGPoint(x: separatorInset, y: itemFrame.maxY - UIScreenPixel), size: CGSize(width: itemLayout.contentSize.width - separatorInset, height: UIScreenPixel))) + } + } + + var removedIds: [Int64] = [] + for (id, itemView) in self.visibleItemViews { + if !validIds.contains(id) { + itemView.host.view?.removeFromSuperview() + itemView.separatorLayer.removeFromSuperlayer() + removedIds.append(id) + } + } + for id in removedIds { + self.visibleItemViews.removeValue(forKey: id) + } + } + + func update(component: ForumTopicListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let itemLayout = ItemLayout(containerSize: availableSize, navigationHeight: component.navigationHeight, itemCount: component.items.count) + self.itemLayout = itemLayout + + self.ignoreScrolling = true + self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) + if self.scrollView.contentSize != itemLayout.contentSize { + self.scrollView.contentSize = itemLayout.contentSize + } + if self.scrollView.scrollIndicatorInsets != itemLayout.itemsInsets { + self.scrollView.scrollIndicatorInsets = itemLayout.itemsInsets + } + self.ignoreScrolling = false + + self.updateVisibleItems(transition: transition, synchronous: false) + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class ForumTopicListScreen: ViewController { + private final class Node: ViewControllerTracingNode { + private weak var controller: ForumTopicListScreen? + + private let context: AccountContext + private let id: EnginePeer.Id + private var presentationData: PresentationData + + private let topicList: ComponentView + + private let forumChannelContext: ForumChannelTopics + private var stateDisposable: Disposable? + private var currentState: ForumChannelTopics.State? + + private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? + + init(controller: ForumTopicListScreen, context: AccountContext, id: EnginePeer.Id) { + self.controller = controller + + self.context = context + self.id = id + self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + + self.topicList = ComponentView() + + self.forumChannelContext = ForumChannelTopics(account: self.context.account, peerId: self.id) + + super.init() + + self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + + self.stateDisposable = (self.forumChannelContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let strongSelf = self else { + return + } + strongSelf.currentState = state + strongSelf.update(transition: .immediate) + }) + } + + deinit { + self.stateDisposable?.dispose() + } + + func createPressed() { + self.forumChannelContext.createTopic(title: "Topic#\(Int.random(in: 0 ..< 100000))") + } + + private func update(transition: Transition) { + if let currentLayout = self.currentLayout { + self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: transition) + } + } + + func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) { + self.currentLayout = (layout, navigationHeight) + + let _ = self.topicList.update( + transition: transition, + component: AnyComponent(ForumTopicListComponent( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + items: self.currentState?.items ?? [], + navigationHeight: navigationHeight, + action: { [weak self] item in + guard let strongSelf = self else { + return + } + strongSelf.controller?.openTopic(item) + } + )), + environment: {}, + containerSize: layout.size + ) + if let topicListView = self.topicList.view { + if topicListView.superview == nil { + if let navigationBar = self.controller?.navigationBar { + self.view.insertSubview(topicListView, belowSubview: navigationBar.view) + } else { + self.view.addSubview(topicListView) + } + } + transition.setFrame(view: topicListView, frame: CGRect(origin: CGPoint(), size: layout.size)) + } + } + } + + private var node: Node { + return self.displayNode as! Node + } + + private let context: AccountContext + private let id: EnginePeer.Id + private var presentationData: PresentationData + private let openTopic: (ForumChannelTopics.Item) -> Void + + public init(context: AccountContext, id: EnginePeer.Id, openTopic: @escaping (ForumChannelTopics.Item) -> Void) { + self.context = context + self.id = id + self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + self.openTopic = openTopic + + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + + //TODO:localize + self.title = "Forum" + + self.navigationItem.setRightBarButton(UIBarButtonItem(title: "Create", style: .plain, target: self, action: #selector(self.createPressed)), animated: false) + } + + public required init(coder aDecoder: NSCoder) { + preconditionFailure() + } + + @objc private func createPressed() { + self.node.createPressed() + } + + override public func loadDisplayNode() { + self.displayNode = Node(controller: self, context: self.context, id: self.id) + + self.displayNodeDidLoad() + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.node.containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: Transition(transition)) + } +} diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 9f7fb52b4a..06212a8476 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -82,12 +82,13 @@ private enum ChatListSearchEntry: Comparable, Identifiable { return ChatListItem( presentationData: presentationData, context: context, - peerGroupId: .root, + chatListLocation: .chatList(groupId: .root), filterData: nil, - index: EngineChatList.Item.Index(pinningIndex: nil, messageIndex: message.index), + index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)), content: .peer( messages: [EngineMessage(message)], peer: EngineRenderedPeer(peer), + threadInfo: nil, combinedReadState: readState.flatMap(EnginePeerReadCounters.init), isRemovedFromTotalUnreadCount: false, presence: nil, @@ -206,12 +207,12 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe } let interaction = ChatListNodeInteraction(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: { - }, peerSelected: { _, _, _ in + }, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { [weak self] peer, message, _ in + }, messageSelected: { [weak self] peer, _, message, _ in if let strongSelf = self { if let index = strongSelf.searchResult.messages.firstIndex(where: { $0.index == message.index }) { if message.id.peerId.namespace == Namespaces.Peer.SecretChat { @@ -238,7 +239,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe return } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _): if let message = messages.first { let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: .message(id: .id(message.id), highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index a8eb39186a..cdd96329e7 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -96,7 +96,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode { placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle - let chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) + let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) if let limit = limit { chatListNode.selectionLimit = limit chatListNode.reachedSelectionLimit = reachedSelectionLimit @@ -140,7 +140,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { self?.openPeer?(peer) } case let .chats(chatsNode): - chatsNode.peerSelected = { [weak self] peer, _, _, _ in + chatsNode.peerSelected = { [weak self] peer, _, _, _, _ in self?.openPeer?(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) } chatsNode.additionalCategorySelected = { [weak self] id in diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 12b7bf24aa..0a8fc85c44 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -425,7 +425,26 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] let createSignal: Signal switch mode { case .generic: - createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds) + if title.contains("*forum") { + createSignal = context.engine.peers.createSupergroup(title: title, description: nil) + |> map(Optional.init) + |> mapError { error -> CreateGroupError in + switch error { + case .generic: + return .generic + case .restricted: + return .restricted + case .tooMuchJoined: + return .tooMuchJoined + case .tooMuchLocationBasedGroups: + return .tooMuchLocationBasedGroups + case let .serverProvided(error): + return .serverProvided(error) + } + } + } else { + createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds) + } case .supergroup: createSignal = context.engine.peers.createSupergroup(title: title, description: nil) |> map(Optional.init) diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 40909b91ee..24b2eabd23 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -12,6 +12,7 @@ import PeerAvatarGalleryUI import SettingsUI import ChatPresentationInterfaceState import AttachmentUI +import ForumTopicListScreen public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) { var found = false @@ -222,3 +223,28 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll return false } + +public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController) -> Signal { + return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: nil) + |> deliverOnMainQueue + |> beforeNext { [weak context, weak navigationController] result in + guard let context = context, let navigationController = navigationController else { + return + } + + let chatLocation: ChatLocation = .replyThread(message: result.message) + + let subject: ChatControllerSubject? + subject = nil + + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: result.isEmpty ? .text : nil, keepStack: .always)) + } + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } +} + +public func navigateToForumChannelImpl(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) { + navigationController.pushViewController(ChatListControllerImpl(context: context, location: .forum(peerId: peerId), controlsHistoryPreload: false, enableDebugActions: false)) +} diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 058685628b..45f2a67007 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -128,7 +128,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { chatListCategories.append(ChatListNodeAdditionalCategory(id: 0, icon: PresentationResourcesItemList.createGroupIcon(self.presentationData.theme), title: self.presentationData.strings.PeerSelection_ImportIntoNewGroup, appearance: .action)) } - self.chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) + self.chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) super.init() @@ -153,7 +153,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self?.requestActivateSearch?() } - self.chatListNode.peerSelected = { [weak self] peer, _, _, _ in + self.chatListNode.peerSelected = { [weak self] peer, _, _, _, _ in self?.chatListNode.clearHighlightAnimated(true) self?.requestOpenPeer?(peer._asPeer()) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 3e5c19e099..175ea8f3f8 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1155,6 +1155,14 @@ public final class SharedAccountContextImpl: SharedAccountContext { navigateToChatControllerImpl(params) } + public func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) { + navigateToForumChannelImpl(context: context, peerId: peerId, navigationController: navigationController) + } + + public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController) -> Signal { + return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, navigationController: navigationController) + } + public func openStorageUsage(context: AccountContext) { guard let navigationController = self.mainWindow?.viewController as? NavigationController else { return @@ -1263,7 +1271,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController { - return ChatListControllerImpl(context: context, groupId: groupId, controlsHistoryPreload: controlsHistoryPreload, hideNetworkActivityStatus: hideNetworkActivityStatus, previewing: previewing, enableDebugActions: enableDebugActions) + return ChatListControllerImpl(context: context, location: .chatList(groupId: EngineChatList.Group(groupId)), controlsHistoryPreload: controlsHistoryPreload, hideNetworkActivityStatus: hideNetworkActivityStatus, previewing: previewing, enableDebugActions: enableDebugActions) } public func makePeerSelectionController(_ params: PeerSelectionControllerParams) -> PeerSelectionController {