diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index fd11c5d378..2096f0f201 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8282,3 +8282,20 @@ Sorry for the inconvenience."; "OwnershipTransfer.EnterPasswordText" = "Please enter your 2-Step Verification password to confirm the action."; "Navigation.AllChats" = "All Chats"; + +"Group.Management.AntiSpam" = "Aggressive Anti-Spam"; +"Group.Management.AntiSpamInfo" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report false positives in Recent Actions."; + +"Group.Management.AntiSpamMagic" = "magic"; +"Group.AdminLog.AntiSpamTitle" = "Telegram Anti-Spam"; +"Group.AdminLog.AntiSpamText" = "You can manage anti-spam settings in Group Info > [Administrators]()."; + +"ChatList.ThreadHideAction" = "Hide"; +"ChatList.ThreadUnhideAction" = "Unhide"; + +"Notification.ForumTopicHidden" = "Topic hidden"; +"Notification.ForumTopicUnhidden" = "Topic unhidden"; +"Notification.ForumTopicHiddenAuthor" = "%1$@ hidden topic"; +"Notification.ForumTopicUnhiddenAuthor" = "%1$@ unhidden topic"; +"Notification.OverviewTopicHidden" = "%1$@ hidden %2$@ %3$@"; +"Notification.OverviewTopicUnhidden" = "%1$@ unhidden %2$@ %3$@"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index aa51262afe..870e25d981 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -693,13 +693,6 @@ public enum ChatListSearchFilter: Equatable { } } -#if ENABLE_WALLET -public enum OpenWalletContext { - case generic - case send(address: String, amount: Int64?, comment: String?) -} -#endif - public let defaultContactLabel: String = "_$!!$_" public enum CreateGroupMode { @@ -839,73 +832,6 @@ public enum PremiumIntroSource { case fasterDownload } -#if ENABLE_WALLET -private final class TonInstanceData { - var config: String? - var blockchainName: String? - var instance: TonInstance? -} - -private final class TonNetworkProxyImpl: TonNetworkProxy { - private let network: Network - - init(network: Network) { - self.network = network - } - - func request(data: Data, timeout timeoutValue: Double, completion: @escaping (TonNetworkProxyResult) -> Void) -> Disposable { - return (walletProxyRequest(network: self.network, data: data) - |> timeout(timeoutValue, queue: .concurrentDefaultQueue(), alternate: .fail(.generic(500, "Local Timeout")))).start(next: { data in - completion(.reponse(data)) - }, error: { error in - switch error { - case let .generic(_, text): - completion(.error(text)) - } - }) - } -} - -public final class StoredTonContext { - private let basePath: String - private let postbox: Postbox - private let network: Network - public let keychain: TonKeychain - private let currentInstance = Atomic(value: TonInstanceData()) - - public init(basePath: String, postbox: Postbox, network: Network, keychain: TonKeychain) { - self.basePath = basePath - self.postbox = postbox - self.network = network - self.keychain = keychain - } - - public func context(config: String, blockchainName: String, enableProxy: Bool) -> TonContext { - return self.currentInstance.with { data -> TonContext in - if let instance = data.instance, data.config == config, data.blockchainName == blockchainName { - return TonContext(instance: instance, keychain: self.keychain) - } else { - data.config = config - let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: blockchainName, proxy: enableProxy ? TonNetworkProxyImpl(network: self.network) : nil) - data.instance = instance - return TonContext(instance: instance, keychain: self.keychain) - } - } - } -} - -public final class TonContext { - public let instance: TonInstance - public let keychain: TonKeychain - - fileprivate init(instance: TonInstance, keychain: TonKeychain) { - self.instance = instance - self.keychain = keychain - } -} - -#endif - public protocol ComposeController: ViewController { } @@ -979,3 +905,23 @@ public struct PremiumConfiguration { } } } + +public struct AntiSpamBotConfiguration { + public static var defaultValue: AntiSpamBotConfiguration { + return AntiSpamBotConfiguration(antiSpamBotId: nil) + } + + public let antiSpamBotId: EnginePeer.Id? + + fileprivate init(antiSpamBotId: EnginePeer.Id?) { + self.antiSpamBotId = antiSpamBotId + } + + public static func with(appConfiguration: AppConfiguration) -> AntiSpamBotConfiguration { + if let data = appConfiguration.data, let string = data["telegram_antispam_user_id"] as? String, let value = Int64(string) { + return AntiSpamBotConfiguration(antiSpamBotId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(value))) + } else { + return .defaultValue + } + } +} diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index f4449a4f4b..85133accc4 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -490,7 +490,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } } -func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: Int64, isPinned: Bool?, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> { +func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: Int64, isPinned: Bool?, isClosed: Bool?, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let strings = presentationData.strings @@ -512,24 +512,27 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: var items: [ContextMenuItem] = [] - if let isPinned = isPinned, channel.hasPermission(.manageTopics) { - items.append(.action(ContextMenuActionItem(text: isPinned ? presentationData.strings.ChatList_Context_Unpin : presentationData.strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in - f(.default) - - let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId) - |> deliverOnMainQueue).start(error: { error in - switch error { - case let .limitReached(count): - if let chatListController = chatListController { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let text = presentationData.strings.ChatList_MaxThreadPinsFinalText(Int32(count)) - chatListController.present(textAlertController(context: context, title: presentationData.strings.Premium_LimitReached, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true), in: .window(.root)) + if let isClosed = isClosed, isClosed { + } else { + if let isPinned = isPinned, channel.hasPermission(.manageTopics) { + items.append(.action(ContextMenuActionItem(text: isPinned ? presentationData.strings.ChatList_Context_Unpin : presentationData.strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId) + |> deliverOnMainQueue).start(error: { error in + switch error { + case let .limitReached(count): + if let chatListController = chatListController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let text = presentationData.strings.ChatList_MaxThreadPinsFinalText(Int32(count)) + chatListController.present(textAlertController(context: context, title: presentationData.strings.Premium_LimitReached, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true), in: .window(.root)) + } + default: + break } - default: - break - } - }) - }))) + }) + }))) + } } var isUnread = false diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index cade9157be..2849fe73ad 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1377,6 +1377,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } strongSelf.setPeerThreadPinned(peerId: peerId, threadId: threadId, isPinned: isPinned) } + self.chatListDisplayNode.containerNode.setPeerThreadHidden = { [weak self] peerId, threadId, isHidden in + guard let strongSelf = self else { + return + } + strongSelf.setPeerThreadHidden(peerId: peerId, threadId: threadId, isHidden: isHidden) + } self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, threadId, animated, activateInput, promoInfo in if let strongSelf = self { @@ -1674,7 +1680,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController 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, threadInfo, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): switch item.index { case .chatList: if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) { @@ -1686,7 +1692,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } else { let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) @@ -1722,7 +1728,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, isClosed: threadInfo?.isClosed, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } } @@ -3456,7 +3462,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.chatListDisplayNode.containerNode.updateState { state in var state = state if updatedValue { - state.archiveShouldBeTemporaryRevealed = false + state.hiddenItemShouldBeTemporaryRevealed = false } state.peerIdWithRevealedOptions = nil return state @@ -3920,6 +3926,35 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.actionDisposables.add(self.context.engine.peers.toggleForumChannelTopicPinned(id: peerId, threadId: threadId).start()) } + private func setPeerThreadHidden(peerId: EnginePeer.Id, threadId: Int64, isHidden: Bool) { + self.actionDisposables.add((self.context.engine.peers.setForumChannelTopicHidden(id: peerId, threadId: threadId, isHidden: isHidden) + |> deliverOnMainQueue).start(completed: { [weak self] in + if let strongSelf = self { + strongSelf.chatListDisplayNode.containerNode.updateState { state in + var state = state + state.hiddenItemShouldBeTemporaryRevealed = false + return state + } + + if isHidden { + strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .hidArchive(title: "General hidden", text: "Pull down to see the general topic.", undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in + guard let strongSelf = self else { + return false + } + if value == .undo { + strongSelf.setPeerThreadHidden(peerId: peerId, threadId: threadId, isHidden: false) + return true + } + return false + }), in: .current) + } else { + strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .revealedArchive(title: "General unhidden", text: "Swipe left on the general topic to hide it.", undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false + }), in: .current) + } + } + })) + } + public func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool = false, deleteGloballyIfPossible: Bool = false, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void) { guard let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else { completion(false) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index c44ba07a34..dd77507bd0 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -183,7 +183,7 @@ private final class ChatListShimmerNode: ASDisplayNode { 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 }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in }, openForumThread: { _, _ in }) interaction.isInlineMode = isInlineMode @@ -532,6 +532,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { previousItemNode.listNode.deletePeerThread = nil previousItemNode.listNode.setPeerThreadStopped = nil previousItemNode.listNode.setPeerThreadPinned = nil + previousItemNode.listNode.setPeerThreadHidden = nil previousItemNode.listNode.peerSelected = nil previousItemNode.listNode.groupSelected = nil previousItemNode.listNode.updatePeerGrouping = nil @@ -576,6 +577,9 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { itemNode.listNode.setPeerThreadPinned = { [weak self] peerId, threadId, isPinned in self?.setPeerThreadPinned?(peerId, threadId, isPinned) } + itemNode.listNode.setPeerThreadHidden = { [weak self] peerId, threadId, isHidden in + self?.setPeerThreadHidden?(peerId, threadId, isHidden) + } itemNode.listNode.peerSelected = { [weak self] peerId, threadId, animated, activateInput, promoInfo in self?.peerSelected?(peerId, threadId, animated, activateInput, promoInfo) } @@ -637,6 +641,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)? var setPeerThreadStopped: ((EnginePeer.Id, Int64, Bool) -> Void)? var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)? + var setPeerThreadHidden: ((EnginePeer.Id, Int64, Bool) -> Void)? var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 7e5420b660..7c3c8fd75f 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -923,7 +923,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }) }))) } else { - if !isPremium, let size = downloadResource?.size, size >= 300 * 1024 * 1024 { + if !isPremium, let size = downloadResource?.size, size >= 150 * 1024 * 1024 { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_IncreaseSpeed, textColor: .primary, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.contextMenu.primaryColor) }, action: { _, f in diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index fa18ee8602..9699c23d7c 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -739,7 +739,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { index = .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)) case .forum: if let threadId = message.threadId, let threadInfo = threadInfo { - chatThreadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadInfo, isOwnedByMe: false, isClosed: false) + chatThreadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadInfo, isOwnedByMe: false, isClosed: false, isHidden: false) index = .forum(pinnedIndex: .none, timestamp: message.index.timestamp, threadId: threadId, namespace: message.index.id.namespace, id: message.index.id.id) } else { index = .chatList( EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)) @@ -1522,7 +1522,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { for thread in allAndFoundThreads { if let peer = thread.renderedPeer.peer, let threadData = thread.threadData, case let .forum(_, _, id, _, _) = thread.index { - entries.append(.topic(peer, ChatListItemContent.ThreadInfo(id: id, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed), index, presentationData.theme, presentationData.strings, .none)) + entries.append(.topic(peer, ChatListItemContent.ThreadInfo(id: id, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden), index, presentationData.theme, presentationData.strings, .none)) index += 1 } } @@ -1842,6 +1842,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in + }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { @@ -3067,7 +3068,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { 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 }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in }, openForumThread: { _, _ in }) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 69cd3d0b1d..bf2f42a507 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -34,12 +34,14 @@ public enum ChatListItemContent { public var info: EngineMessageHistoryThread.Info public var isOwnedByMe: Bool public var isClosed: Bool + public var isHidden: Bool - public init(id: Int64, info: EngineMessageHistoryThread.Info, isOwnedByMe: Bool, isClosed: Bool) { + public init(id: Int64, info: EngineMessageHistoryThread.Info, isOwnedByMe: Bool, isClosed: Bool, isHidden: Bool) { self.id = id self.info = info self.isOwnedByMe = isOwnedByMe self.isClosed = isClosed + self.isHidden = isHidden } } @@ -333,7 +335,7 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr return options } -private func forumGeneralRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isMuted: Bool?, isEditing: Bool, canHide: Bool, hiddenByDefault: Bool) -> [ItemListRevealOption] { +private func forumGeneralRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isMuted: Bool?, isClosed: Bool, isEditing: Bool, canOpenClose: Bool, canHide: Bool, hiddenByDefault: Bool) -> [ItemListRevealOption] { var options: [ItemListRevealOption] = [] if !isEditing { if let isMuted = isMuted { @@ -344,12 +346,21 @@ private func forumGeneralRevealOptions(strings: PresentationStrings, theme: Pres } } } + if canOpenClose && !hiddenByDefault { + if !isEditing { + if !isClosed { +// options.append(ItemListRevealOption(key: RevealOptionKey.close.rawValue, title: strings.ChatList_CloseAction, icon: closeIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor)) + } else { + options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + } + } + } if canHide { if !isEditing { if hiddenByDefault { - options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_UnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_ThreadUnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_HideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_ThreadHideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor)) } } } @@ -558,10 +569,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.view.addSubview(self.titleTopicIconView) } - static func asyncLayout(_ currentNode: TopicItemNode?) -> (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode) { + static func asyncLayout(_ currentNode: TopicItemNode?) -> (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ threadId: Int64, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode) { let makeTopicTitleLayout = TextNode.asyncLayout(currentNode?.topicTitleNode) - return { constrainedWidth, context, theme, title, iconId, iconColor in + return { constrainedWidth, context, theme, threadId, title, iconId, iconColor in let remainingWidth = max(1.0, constrainedWidth - (18.0 + 2.0)) let topicTitleArguments = TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: remainingWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)) @@ -579,7 +590,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let titleTopicIconContent: EmojiStatusComponent.Content - if let fileId = iconId, fileId != 0 { + if threadId == 1 { + titleTopicIconContent = .image(image: PresentationResourcesChatList.generalTopicSmallIcon(theme)) + } else if let fileId = iconId, fileId != 0 { titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(2)) } else { titleTopicIconContent = .topic(title: String(title.string.prefix(1)), color: iconColor, size: CGSize(width: 18.0, height: 18.0)) @@ -654,7 +667,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topics: [(id: Int64, title: NSAttributedString, iconId: Int64?, iconColor: Int32)]) -> (CGSize, () -> CGRect?) { let makeAuthorLayout = TextNode.asyncLayout(self.authorNode) - var makeExistingTopicLayouts: [Int64: (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode)] = [:] + var makeExistingTopicLayouts: [Int64: (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ threadId: Int64, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode)] = [:] for (topicId, topicNode) in self.topicNodes { makeExistingTopicLayouts[topicId] = TopicItemNode.asyncLayout(topicNode) } @@ -686,7 +699,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let makeTopicLayout = makeExistingTopicLayouts[topic.id] ?? TopicItemNode.asyncLayout(nil) - let (topicSize, topicApply) = makeTopicLayout(remainingWidth, context, theme, topic.title, topic.iconId, topic.iconColor) + let (topicSize, topicApply) = makeTopicLayout(remainingWidth, context, theme, topic.id, topic.title, topic.iconId, topic.iconColor) topicsSizeAndApply.append((topic.id, topicSize, topicApply)) remainingWidth -= topicSize.width + 4.0 @@ -2016,7 +2029,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if case let .chatList(index) = item.index, index.messageIndex.id.peerId == item.context.account.peerId { isAccountPeer = true } - if !isPeerGroup && !isAccountPeer { + if !isPeerGroup && !isAccountPeer && threadInfo == nil { if displayAsMessage { switch item.content { case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): @@ -2223,22 +2236,22 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if item.enableContextActions { if case .forum = item.chatListLocation { if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer { + var canOpenClose = false + if channel.flags.contains(.isCreator) { + canOpenClose = true + } else if channel.hasPermission(.manageTopics) { + canOpenClose = true + } else if let threadInfo = threadInfo, threadInfo.isOwnedByMe { + canOpenClose = true + } + let canDelete = channel.hasPermission(.deleteAllMessages) + var isClosed = false + if let threadInfo { + isClosed = threadInfo.isClosed + } if let threadInfo, threadInfo.id == 1 { - peerRevealOptions = forumGeneralRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isEditing: item.editing, canHide: channel.flags.contains(.isCreator) || channel.hasPermission(.pinMessages), hiddenByDefault: false) + peerRevealOptions = forumGeneralRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canHide: channel.flags.contains(.isCreator) || channel.hasPermission(.pinMessages), hiddenByDefault: threadInfo.isHidden) } else { - var canOpenClose = false - if channel.flags.contains(.isCreator) { - canOpenClose = true - } else if channel.hasPermission(.manageTopics) { - canOpenClose = true - } else if let threadInfo = threadInfo, threadInfo.isOwnedByMe { - canOpenClose = true - } - let canDelete = channel.hasPermission(.deleteAllMessages) - var isClosed = false - if let threadInfo { - isClosed = threadInfo.isClosed - } peerRevealOptions = forumThreadRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canDelete: canDelete) } peerLeftRevealOptions = [] @@ -2312,6 +2325,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.cachedChatListText = chatListText strongSelf.cachedChatListSearchResult = chatListSearchResult strongSelf.onlineIsVoiceChat = onlineIsVoiceChat + + strongSelf.clipsToBounds = true if case .groupReference = item.content { strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) @@ -2480,7 +2495,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let avatarIconContent: EmojiStatusComponent.Content - if let fileId = threadInfo.info.icon, fileId != 0 { + if threadInfo.id == 1 { + avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(item.presentationData.theme)) + } else if let fileId = threadInfo.info.icon, fileId != 0 { avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) } else { avatarIconContent = .topic(title: String(threadInfo.info.title.prefix(1)), color: threadInfo.info.iconColor, size: CGSize(width: 32.0, height: 32.0)) @@ -3419,6 +3436,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { item.interaction.setPeerThreadPinned(peerId, threadId, true) case RevealOptionKey.unpin.rawValue: item.interaction.setPeerThreadPinned(peerId, threadId, false) + case RevealOptionKey.hide.rawValue: + item.interaction.setPeerThreadHidden(peerId, threadId, true) + case RevealOptionKey.unhide.rawValue: + item.interaction.setPeerThreadHidden(peerId, threadId, false) default: break } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 2737726f3f..5b361a601d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -82,6 +82,7 @@ public final class ChatListNodeInteraction { let deletePeerThread: (EnginePeer.Id, Int64) -> Void let setPeerThreadStopped: (EnginePeer.Id, Int64, Bool) -> Void let setPeerThreadPinned: (EnginePeer.Id, Int64, Bool) -> Void + let setPeerThreadHidden: (EnginePeer.Id, Int64, Bool) -> Void let updatePeerGrouping: (EnginePeer.Id, Bool) -> Void let togglePeerMarkedUnread: (EnginePeer.Id, Bool) -> Void let toggleArchivedFolderHiddenByDefault: () -> Void @@ -121,6 +122,7 @@ public final class ChatListNodeInteraction { deletePeerThread: @escaping (EnginePeer.Id, Int64) -> Void, setPeerThreadStopped: @escaping (EnginePeer.Id, Int64, Bool) -> Void, setPeerThreadPinned: @escaping (EnginePeer.Id, Int64, Bool) -> Void, + setPeerThreadHidden: @escaping (EnginePeer.Id, Int64, Bool) -> Void, updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void, togglePeerMarkedUnread: @escaping (EnginePeer.Id, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, @@ -147,6 +149,7 @@ public final class ChatListNodeInteraction { self.deletePeerThread = deletePeerThread self.setPeerThreadStopped = setPeerThreadStopped self.setPeerThreadPinned = setPeerThreadPinned + self.setPeerThreadHidden = setPeerThreadHidden self.updatePeerGrouping = updatePeerGrouping self.togglePeerMarkedUnread = togglePeerMarkedUnread self.toggleArchivedFolderHiddenByDefault = toggleArchivedFolderHiddenByDefault @@ -208,7 +211,7 @@ public struct ChatListNodeState: Equatable { public var peerInputActivities: ChatListNodePeerInputActivities? public var pendingRemovalItemIds: Set public var pendingClearHistoryPeerIds: Set - public var archiveShouldBeTemporaryRevealed: Bool + public var hiddenItemShouldBeTemporaryRevealed: Bool public var selectedAdditionalCategoryIds: Set public var hiddenPsaPeerId: EnginePeer.Id? public var foundPeers: [(EnginePeer, EnginePeer?)] @@ -226,7 +229,7 @@ public struct ChatListNodeState: Equatable { peerInputActivities: ChatListNodePeerInputActivities?, pendingRemovalItemIds: Set, pendingClearHistoryPeerIds: Set, - archiveShouldBeTemporaryRevealed: Bool, + hiddenItemShouldBeTemporaryRevealed: Bool, hiddenPsaPeerId: EnginePeer.Id?, selectedThreadIds: Set ) { @@ -240,7 +243,7 @@ public struct ChatListNodeState: Equatable { self.peerInputActivities = peerInputActivities self.pendingRemovalItemIds = pendingRemovalItemIds self.pendingClearHistoryPeerIds = pendingClearHistoryPeerIds - self.archiveShouldBeTemporaryRevealed = archiveShouldBeTemporaryRevealed + self.hiddenItemShouldBeTemporaryRevealed = hiddenItemShouldBeTemporaryRevealed self.hiddenPsaPeerId = hiddenPsaPeerId self.selectedThreadIds = selectedThreadIds } @@ -276,7 +279,7 @@ public struct ChatListNodeState: Equatable { if lhs.pendingClearHistoryPeerIds != rhs.pendingClearHistoryPeerIds { return false } - if lhs.archiveShouldBeTemporaryRevealed != rhs.archiveShouldBeTemporaryRevealed { + if lhs.hiddenItemShouldBeTemporaryRevealed != rhs.hiddenItemShouldBeTemporaryRevealed { return false } if lhs.hiddenPsaPeerId != rhs.hiddenPsaPeerId { @@ -312,7 +315,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction.additionalCategorySelected(id) } ), directionHint: entry.directionHint) - case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems, revealed): switch mode { case .chatList: return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -344,7 +347,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL selected: selected, header: nil, enableContextActions: true, - hiddenOffset: false, + hiddenOffset: threadInfo?.isHidden == true && !revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters): @@ -534,7 +537,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL 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, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems, revealed): switch mode { case .chatList: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -566,7 +569,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL selected: selected, header: nil, enableContextActions: true, - hiddenOffset: false, + hiddenOffset: threadInfo?.isHidden == true && !revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) case let .peers(filter, isSelecting, _, filters): @@ -790,6 +793,7 @@ public final class ChatListNode: ListView { public var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)? public var setPeerThreadStopped: ((EnginePeer.Id, Int64, Bool) -> Void)? public var setPeerThreadPinned: ((EnginePeer.Id, Int64, Bool) -> Void)? + public var setPeerThreadHidden: ((EnginePeer.Id, Int64, Bool) -> Void)? public var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? public var presentAlert: ((String) -> Void)? public var present: ((ViewController) -> Void)? @@ -901,7 +905,7 @@ public final class ChatListNode: ListView { isSelecting = true } - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), archiveShouldBeTemporaryRevealed: false, hiddenPsaPeerId: nil, selectedThreadIds: Set()) + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), foundPeers: [], selectedPeerMap: [:], selectedAdditionalCategoryIds: Set(), peerInputActivities: nil, pendingRemovalItemIds: Set(), pendingClearHistoryPeerIds: Set(), hiddenItemShouldBeTemporaryRevealed: false, hiddenPsaPeerId: nil, selectedThreadIds: Set()) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme @@ -1111,6 +1115,8 @@ public final class ChatListNode: ListView { self?.setPeerThreadStopped?(peerId, threadId, isStopped) }, setPeerThreadPinned: { [weak self] peerId, threadId, isPinned in self?.setPeerThreadPinned?(peerId, threadId, isPinned) + }, setPeerThreadHidden: { [weak self] peerId, threadId, isHidden in + self?.setPeerThreadHidden?(peerId, threadId, isHidden) }, updatePeerGrouping: { [weak self] peerId, group in self?.updatePeerGrouping?(peerId, group) }, togglePeerMarkedUnread: { [weak self, weak context] peerId, animated in @@ -1242,7 +1248,7 @@ public final class ChatListNode: ListView { let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location) let entries = rawEntries.filter { entry in switch entry { - case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): switch mode { case .chatList: return true @@ -1414,9 +1420,13 @@ public final class ChatListNode: ListView { var didIncludeRemovingPeerId = false var didIncludeHiddenByDefaultArchive = false + var didIncludeHiddenThread = false if let previous = previousView { for entry in previous.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(index, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if let threadInfo, threadInfo.isHidden { + didIncludeHiddenThread = true + } if case let .chatList(chatListIndex) = index { if chatListIndex.pinningIndex != nil { previousPinnedChats.append(chatListIndex.messageIndex.id.peerId) @@ -1440,8 +1450,13 @@ public final class ChatListNode: ListView { var doesIncludeRemovingPeerId = false var doesIncludeArchive = false var doesIncludeHiddenByDefaultArchive = false + + var doesIncludeHiddenThread = false for entry in processedView.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(index, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if let threadInfo, threadInfo.isHidden { + doesIncludeHiddenThread = true + } if case let .chatList(index) = index, index.pinningIndex != nil { updatedPinnedChats.append(index.messageIndex.id.peerId) } else if case let .forum(pinnedIndex, _, threadId, _, _) = index { @@ -1474,12 +1489,18 @@ public final class ChatListNode: ListView { if doesIncludeRemovingPeerId != didIncludeRemovingPeerId { disableAnimations = false } - if hideArchivedFolderByDefault && previousState.archiveShouldBeTemporaryRevealed != state.archiveShouldBeTemporaryRevealed && doesIncludeArchive { + if hideArchivedFolderByDefault && previousState.hiddenItemShouldBeTemporaryRevealed != state.hiddenItemShouldBeTemporaryRevealed && doesIncludeArchive { disableAnimations = false } if didIncludeHiddenByDefaultArchive != doesIncludeHiddenByDefaultArchive { disableAnimations = false } + if previousState.hiddenItemShouldBeTemporaryRevealed != state.hiddenItemShouldBeTemporaryRevealed && doesIncludeHiddenThread { + disableAnimations = false + } + if didIncludeHiddenThread != doesIncludeHiddenThread { + disableAnimations = false + } } if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault { @@ -1534,7 +1555,7 @@ public final class ChatListNode: ListView { strongSelf.enqueueHistoryPreloadUpdate() } - var archiveVisible = false + var isHiddenItemVisible = false if let range = range.visibleRange { let entryCount = chatListView.filteredEntries.count for i in range.firstIndex ..< range.lastIndex { @@ -1543,19 +1564,22 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case .PeerEntry: + case let .PeerEntry(_, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _): + if let threadInfo, threadInfo.isHidden { + isHiddenItemVisible = true + } break case .GroupReferenceEntry: - archiveVisible = true + isHiddenItemVisible = true default: break } } } - if !archiveVisible && strongSelf.currentState.archiveShouldBeTemporaryRevealed { + if !isHiddenItemVisible && strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed { strongSelf.updateState { state in var state = state - state.archiveShouldBeTemporaryRevealed = false + state.hiddenItemShouldBeTemporaryRevealed = false return state } } @@ -1747,7 +1771,7 @@ 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 { @@ -1774,7 +1798,7 @@ public final class ChatListNode: ListView { var itemId: EngineChatList.PinnedItem.Id? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if case let .chatList(index) = index { itemId = .peer(index.messageIndex.id.peerId) } @@ -1820,7 +1844,7 @@ public final class ChatListNode: ListView { var referenceId: Int64? var beforeAll = false switch toEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): if promoInfo != nil { beforeAll = true } else { @@ -1840,7 +1864,7 @@ public final class ChatListNode: ListView { var itemId: Int64? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if case let .forum(_, _, threadId, _, _) = index { itemId = threadId } @@ -1921,10 +1945,10 @@ public final class ChatListNode: ListView { case let .known(value): revealHiddenItems = value <= 54.0 } - if !revealHiddenItems && strongSelf.currentState.archiveShouldBeTemporaryRevealed { + if !revealHiddenItems && strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed { strongSelf.updateState { state in var state = state - state.archiveShouldBeTemporaryRevealed = false + state.hiddenItemShouldBeTemporaryRevealed = false return state } } @@ -1961,25 +1985,30 @@ public final class ChatListNode: ListView { } strongSelf.scrolledAtTopValue = atTop strongSelf.contentOffsetChanged?(offset) - if revealHiddenItems && !strongSelf.currentState.archiveShouldBeTemporaryRevealed { - var isHiddenArchiveVisible = false + if revealHiddenItems && !strongSelf.currentState.hiddenItemShouldBeTemporaryRevealed { + var isHiddenItemVisible = false strongSelf.forEachItemNode({ itemNode in if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { + if case let .peer(_, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let threadInfo { + if threadInfo.isHidden { + isHiddenItemVisible = true + } + } if case let .groupReference(_, _, _, _, hiddenByDefault) = item.content { if hiddenByDefault { - isHiddenArchiveVisible = true + isHiddenItemVisible = true } } } }) - if isHiddenArchiveVisible { + if isHiddenItemVisible { if strongSelf.hapticFeedback == nil { strongSelf.hapticFeedback = HapticFeedback() } strongSelf.hapticFeedback?.impact(.medium) strongSelf.updateState { state in var state = state - state.archiveShouldBeTemporaryRevealed = true + state.hiddenItemShouldBeTemporaryRevealed = true return state } } @@ -2104,7 +2133,7 @@ public final class ChatListNode: ListView { 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 { @@ -2436,7 +2465,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 @@ -2483,10 +2512,10 @@ public final class ChatListNode: ListView { 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 { @@ -2495,7 +2524,7 @@ 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!) } } @@ -2573,7 +2602,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 diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 78ea88f682..0356bf510c 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -67,7 +67,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { hasFailedMessages: Bool, isContact: Bool, forumTopicData: EngineChatList.ForumTopicData?, - topForumTopicItems: [EngineChatList.ForumTopicData] + topForumTopicItems: [EngineChatList.ForumTopicData], + revealed: 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) @@ -78,7 +79,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .index(.chatList(.absoluteUpperBound)) - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return .index(index) case let .HoleEntry(holeIndex, _): return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex))) @@ -95,7 +96,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .Header - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): switch index { case let .chatList(index): return .PeerId(index.messageIndex.id.peerId.toInt64()) @@ -125,9 +126,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } - case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact, lhsForumThreadTitle, lhsTopForumTopicItems): + case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact, lhsForumThreadTitle, lhsTopForumTopicItems, lhsRevealed): switch rhs { - case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact, rhsForumThreadTitle, rhsTopForumTopicItems): + case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact, rhsForumThreadTitle, rhsTopForumTopicItems, rhsRevealed): if lhsIndex != rhsIndex { return false } @@ -228,6 +229,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhsTopForumTopicItems != rhsTopForumTopicItems { return false } + if lhsRevealed != rhsRevealed { + return false + } return true default: return false @@ -394,10 +398,32 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState var threadInfo: ChatListItemContent.ThreadInfo? if let threadData = entry.threadData, let threadId = threadId { - threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed) + threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden) } - 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: 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, forumTopicData: entry.forumTopicData, topForumTopicItems: entry.topForumTopicItems)) + 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: 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, + forumTopicData: entry.forumTopicData, + topForumTopicItems: entry.topForumTopicItems, + revealed: threadId == 1 && state.hiddenItemShouldBeTemporaryRevealed + )) } if !view.hasLater { var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) @@ -432,7 +458,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState hasFailedMessages: false, isContact: false, forumTopicData: nil, - topForumTopicItems: [] + topForumTopicItems: [], + revealed: false )) if foundPinningIndex != 0 { foundPinningIndex -= 1 @@ -440,7 +467,29 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState } } - 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, forumTopicData: nil, topForumTopicItems: [])) + 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, + forumTopicData: nil, + topForumTopicItems: [], + revealed: false + )) } else { if !filteredAdditionalItemEntries.isEmpty { for item in filteredAdditionalItemEntries.reversed() { @@ -476,7 +525,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState isRemovedFromTotalUnreadCount: item.item.isMuted, draftState: draftState, peer: item.item.renderedPeer, - threadInfo: item.item.threadData.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0.info, isOwnedByMe: $0.isOwnedByMe, isClosed: $0.isClosed) }, + threadInfo: item.item.threadData.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0.info, isOwnedByMe: $0.isOwnedByMe, isClosed: $0.isClosed, isHidden: $0.isHidden) }, presence: item.item.presence, hasUnseenMentions: item.item.hasUnseenMentions, hasUnseenReactions: item.item.hasUnseenReactions, @@ -488,7 +537,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState hasFailedMessages: item.item.hasFailed, isContact: item.item.isContact, forumTopicData: item.item.forumTopicData, - topForumTopicItems: item.item.topForumTopicItems + topForumTopicItems: item.item.topForumTopicItems, + revealed: threadId == 1 && state.hiddenItemShouldBeTemporaryRevealed )) if pinningIndex != 0 { pinningIndex -= 1 @@ -508,7 +558,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState message: groupReference.topMessage, editing: state.editing, unreadCount: groupReference.unreadCount, - revealed: state.archiveShouldBeTemporaryRevealed, + revealed: state.hiddenItemShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault )) if pinningIndex != 0 { diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index f0d7fc81dc..46aa156ee5 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -810,7 +810,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame.offsetBy(dx: 0.0, dy: additionalVisibleOffsetY), beginWithCurrentState: true) if let contentNode = contentNode { - contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size), beginWithCurrentState: true) + var contentFrame = CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size) + if case let .extracted(extracted) = self.source, extracted.centerVertically, contentFrame.midX > layout.size.width / 2.0 { + contentFrame.origin.x = layout.size.width - contentFrame.maxX + } + contentTransition.updateFrame(node: contentNode, frame: contentFrame, beginWithCurrentState: true) } let contentHeight: CGFloat @@ -863,20 +867,38 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - let animationInContentDistance: CGFloat + let animationInContentYDistance: CGFloat let currentContentScreenFrame: CGRect if let contentNode = contentNode { if let animateClippingFromContentAreaInScreenSpace = contentNode.animateClippingFromContentAreaInScreenSpace { self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(x: 0.0, y: animateClippingFromContentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: animateClippingFromContentAreaInScreenSpace.height)), to: CGRect(origin: CGPoint(), size: layout.size), duration: 0.2) self.clippingNode.layer.animateBoundsOriginYAdditive(from: animateClippingFromContentAreaInScreenSpace.minY, to: 0.0, duration: 0.2) } - + currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view) - animationInContentDistance = currentContentLocalFrame.maxY - currentContentScreenFrame.maxY + animationInContentYDistance = currentContentLocalFrame.maxY - currentContentScreenFrame.maxY + + var animationInContentXDistance: CGFloat = 0.0 + let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX + let contentWidth = contentNode.containingItem.view.bounds.size.width + if case let .extracted(extracted) = self.source, extracted.centerVertically, contentX + contentWidth > layout.size.width / 2.0 { + let fixedContentX = layout.size.width - (contentX + contentWidth) + animationInContentXDistance = fixedContentX - contentX + + contentNode.layer.animateSpring( + from: -animationInContentXDistance as NSNumber, to: 0.0 as NSNumber, + keyPath: "position.x", + duration: duration, + delay: 0.0, + initialVelocity: 0.0, + damping: springDamping, + additive: true + ) + } contentNode.layer.animateSpring( - from: -animationInContentDistance as NSNumber, to: 0.0 as NSNumber, + from: -animationInContentYDistance as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", duration: duration, delay: 0.0, @@ -885,7 +907,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo additive: true ) } else { - animationInContentDistance = 0.0 + animationInContentYDistance = 0.0 currentContentScreenFrame = contentRect } @@ -926,7 +948,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo actionsVerticalTransitionDirection = 1.0 } } - let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing + let actionsPositionDeltaYDistance = -animationInContentYDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing self.actionsStackNode.layer.animateSpring( from: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)), to: NSValue(cgPoint: CGPoint()), @@ -939,7 +961,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo ) if let reactionContextNode = self.reactionContextNode { - let reactionsPositionDeltaYDistance = -animationInContentDistance + let reactionsPositionDeltaYDistance = -animationInContentYDistance reactionContextNode.layer.animateSpring( from: NSValue(cgPoint: CGPoint(x: 0.0, y: reactionsPositionDeltaYDistance)), to: NSValue(cgPoint: CGPoint()), @@ -1043,13 +1065,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view) - let animationInContentDistance: CGFloat + let animationInContentYDistance: CGFloat switch result { case .default, .custom: - animationInContentDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY + animationInContentYDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY case .dismissWithoutContent: - animationInContentDistance = 0.0 + animationInContentYDistance = 0.0 if let contentNode = contentNode { contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false) } @@ -1075,10 +1097,28 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let contentNode = contentNode { contentNode.containingItem.willUpdateIsExtractedToContextPreview?(false, transition) - contentNode.offsetContainerNode.position = contentNode.offsetContainerNode.position.offsetBy(dx: 0.0, dy: -animationInContentDistance) + var animationInContentXDistance: CGFloat = 0.0 + let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX + let contentWidth = contentNode.containingItem.view.bounds.size.width + if case let .extracted(extracted) = self.source, extracted.centerVertically, contentX + contentWidth > layout.size.width / 2.0 { + let fixedContentX = layout.size.width - (contentX + contentWidth) + animationInContentXDistance = contentX - fixedContentX + + contentNode.offsetContainerNode.layer.animate( + from: -animationInContentXDistance as NSNumber, + to: 0.0 as NSNumber, + keyPath: "position.x", + timingFunction: timingFunction, + duration: duration, + delay: 0.0, + additive: true + ) + } + + contentNode.offsetContainerNode.position = contentNode.offsetContainerNode.position.offsetBy(dx: animationInContentXDistance, dy: -animationInContentYDistance) let reactionContextNodeIsAnimatingOut = self.reactionContextNodeIsAnimatingOut contentNode.offsetContainerNode.layer.animate( - from: animationInContentDistance as NSNumber, + from: animationInContentYDistance as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", timingFunction: timingFunction, @@ -1132,7 +1172,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX } - let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing + let actionsPositionDeltaYDistance = -animationInContentYDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing self.actionsStackNode.layer.animate( from: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)), diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index b481206222..e2cc9fd261 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -81,6 +81,7 @@ public final class HashtagSearchController: TelegramBaseController { }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in + }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { diff --git a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift index 326474c424..7e0cd2563e 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift @@ -13,6 +13,7 @@ public enum ItemListSwitchItemNodeType { public class ItemListSwitchItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let icon: UIImage? let title: String let value: Bool let type: ItemListSwitchItemNodeType @@ -27,8 +28,9 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem { let activatedWhileDisabled: () -> Void public let tag: ItemListItemTag? - public init(presentationData: ItemListPresentationData, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) { + public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) { self.presentationData = presentationData + self.icon = icon self.title = title self.value = value self.type = type @@ -120,6 +122,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { private let highlightedBackgroundNode: ASDisplayNode private let maskNode: ASImageNode + private let iconNode: ASImageNode private let titleNode: TextNode private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl private let switchGestureNode: ASDisplayNode @@ -147,6 +150,10 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode.isLayerBacked = true + self.iconNode = ASImageNode() + self.iconNode.isLayerBacked = true + self.iconNode.displaysAsynchronously = false + self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false switch type { @@ -206,11 +213,15 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) var updatedTheme: PresentationTheme? - if currentItem?.presentationData.theme !== item.presentationData.theme { updatedTheme = item.presentationData.theme } + var updateIcon = false + if currentItem?.icon != item.icon { + updateIcon = true + } + switch item.style { case .plain: itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor @@ -224,6 +235,11 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { insets = itemListNeighborsGroupedInsets(neighbors, params) } + var leftInset = 16.0 + params.leftInset + if let _ = item.icon { + leftInset += 43.0 + } + if item.disableLeadingInset { insets.top = 0.0 insets.bottom = 0.0 @@ -260,6 +276,20 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { } strongSelf.activateArea.accessibilityTraits = accessibilityTraits + if let icon = item.icon { + if strongSelf.iconNode.supernode == nil { + strongSelf.addSubnode(strongSelf.iconNode) + } + if updateIcon { + strongSelf.iconNode.image = icon + } + let iconY = floor((layout.contentSize.height - icon.size.height) / 2.0) + strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - icon.size.width) / 2.0), y: iconY), size: icon.size) + } else if strongSelf.iconNode.supernode != nil { + strongSelf.iconNode.image = nil + strongSelf.iconNode.removeFromSupernode() + } + let transition: ContainedViewLayoutTransition if animated { transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) @@ -301,8 +331,6 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { let _ = titleApply() - let leftInset = 16.0 + params.leftInset - switch item.style { case .plain: if strongSelf.backgroundNode.supernode != nil { @@ -345,7 +373,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { let bottomStripeInset: CGFloat switch neighbors.bottom { case .sameSection(false): - bottomStripeInset = 16.0 + params.leftInset + bottomStripeInset = leftInset strongSelf.bottomStripeNode.isHidden = false default: bottomStripeInset = 0.0 diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index d9a1964367..2757deaabc 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1356,13 +1356,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } if let undoOverlayController = strongSelf.undoOverlayController { - undoOverlayController.content = .image(image: image ?? UIImage(), text: text) + undoOverlayController.content = .image(image: image ?? UIImage(), title: nil, text: text, undo: true) } else { var elevatedLayout = true if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass { elevatedLayout = false } - let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), text: text), elevatedLayout: elevatedLayout, action: { [weak self] action in + let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), title: nil, text: text, undo: true), elevatedLayout: elevatedLayout, action: { [weak self] action in guard let strongSelf = self else { return true } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 969973b787..cfd304e885 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -24,14 +24,16 @@ private final class ChannelAdminsControllerArguments { let removeAdmin: (PeerId) -> Void let addAdmin: () -> Void let openAdmin: (ChannelParticipant) -> Void + let updateAntiSpamEnabled: (Bool) -> Void - init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removeAdmin: @escaping (PeerId) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void) { + init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removeAdmin: @escaping (PeerId) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void, updateAntiSpamEnabled: @escaping (Bool) -> Void) { self.context = context self.openRecentActions = openRecentActions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.removeAdmin = removeAdmin self.addAdmin = addAdmin self.openAdmin = openAdmin + self.updateAntiSpamEnabled = updateAntiSpamEnabled } } @@ -47,6 +49,8 @@ private enum ChannelAdminsEntryStableId: Hashable { private enum ChannelAdminsEntry: ItemListNodeEntry { case recentActions(PresentationTheme, String) + case antiSpam(PresentationTheme, String, Bool) + case antiSpamInfo(PresentationTheme, String) case adminsHeader(PresentationTheme, String) case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Bool, Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool, Bool) @@ -55,7 +59,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { var section: ItemListSectionId { switch self { - case .recentActions: + case .recentActions, .antiSpam, .antiSpamInfo: return ChannelAdminsSection.administration.rawValue case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo: return ChannelAdminsSection.admins.rawValue @@ -66,6 +70,10 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { switch self { case .recentActions: return .index(0) + case .antiSpam: + return .index(1) + case .antiSpamInfo: + return .index(2) case .adminsHeader: return .index(3) case .addAdmin: @@ -85,6 +93,18 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { } else { return false } + case let .antiSpam(lhsTheme, lhsText, lhsValue): + if case let .antiSpam(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .antiSpamInfo(lhsTheme, lhsText): + if case let .antiSpamInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } case let .adminsHeader(lhsTheme, lhsText): if case let .adminsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true @@ -146,16 +166,30 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { switch lhs { case .recentActions: return true - case .adminsHeader: + case .antiSpam: switch rhs { case .recentActions: return false default: return true } + case .antiSpamInfo: + switch rhs { + case .recentActions, .antiSpam: + return false + default: + return true + } + case .adminsHeader: + switch rhs { + case .recentActions, .antiSpam, .antiSpamInfo: + return false + default: + return true + } case let .adminPeerItem(_, _, _, _, _, index, _, _, _, _): switch rhs { - case .recentActions, .adminsHeader, .addAdmin: + case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin: return false case let .adminPeerItem(_, _, _, _, _, rhsIndex, _, _, _, _): return index < rhsIndex @@ -164,7 +198,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { } case .addAdmin: switch rhs { - case .recentActions, .adminsHeader, .addAdmin: + case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin: return false default: return true @@ -178,9 +212,15 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { let arguments = arguments as! ChannelAdminsControllerArguments switch self { case let .recentActions(_, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), title: text, label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openRecentActions() }) + case let .antiSpam(_, text, value): + return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/AntiSpam")?.precomposed(), title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + arguments.updateAntiSpamEnabled(value) + }) + case let .antiSpamInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .adminsHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, _, participant, editing, enabled, hasAction): @@ -298,20 +338,22 @@ private struct ChannelAdminsControllerState: Equatable { } } -private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelAdminsEntry] { +private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamEnabled: Bool) -> [ChannelAdminsEntry] { if participants == nil || participants?.count == nil { return [] } var entries: [ChannelAdminsEntry] = [] - if let peer = view.peers[view.peerId] as? TelegramChannel { var isGroup = false if case .group = peer.info { isGroup = true - entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog)) - } else { - entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog)) + } + entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog)) + + if isGroup && peer.hasPermission(.deleteAllMessages) { + entries.append(.antiSpam(presentationData.theme, presentationData.strings.Group_Management_AntiSpam, antiSpamEnabled)) + entries.append(.antiSpamInfo(presentationData.theme, presentationData.strings.Group_Management_AntiSpamInfo)) } if let participants = participants { @@ -492,8 +534,31 @@ public func channelAdminsController(context: AccountContext, updatedPresentation let upgradeDisposable = MetaDisposable() actionsDisposable.add(upgradeDisposable) + let updateAntiSpamDisposable = MetaDisposable() + actionsDisposable.add(updateAntiSpamDisposable) + let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil) + let antiSpamBotConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + + let resolveAntiSpamPeerDisposable = MetaDisposable() + if let antiSpamBotId = antiSpamBotConfiguration.antiSpamBotId { + resolveAntiSpamPeerDisposable.set( + (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: antiSpamBotId)) + |> mapToSignal { peer -> Signal in + if let _ = peer { + return .never() + } else { + return context.engine.peers.updatedRemotePeer(peer: .user(id: antiSpamBotId.id._internalGetInt64Value(), accessHash: 0)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .never() + } + } + }).start() + ) + } + var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)? let currentPeerId = ValuePromise(initialPeerId) @@ -626,6 +691,12 @@ public func channelAdminsController(context: AccountContext, updatedPresentation pushControllerImpl?(channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership)) }) + }, updateAntiSpamEnabled: { value in + let _ = (currentPeerId.get() + |> take(1) + |> deliverOnMainQueue).start(next: { peerId in + updateAntiSpamDisposable.set(context.engine.peers.toggleAntiSpamProtection(peerId: peerId, enabled: value).start()) + }) }) let membersAndLoadMoreControlValue = Atomic<(Disposable, PeerChannelMemberCategoryControl?)?>(value: nil) @@ -700,9 +771,19 @@ public func channelAdminsController(context: AccountContext, updatedPresentation var previousPeers: [RenderedChannelParticipant]? let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData - let signal = combineLatest(queue: .mainQueue(), presentationData, statePromise.get(), peerView.get(), adminsPromise.get() |> deliverOnMainQueue) + let signal = combineLatest( + queue: .mainQueue(), + presentationData, + statePromise.get(), + peerView.get(), + adminsPromise.get(), + currentPeerId.get() + |> mapToSignal { peerId -> Signal in + return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AntiSpamEnabled(id: peerId)) + } + ) |> deliverOnMainQueue - |> map { presentationData, state, view, admins -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, view, admins, antiSpamEnabled -> (ItemListControllerState, (ItemListNodeState, Any)) in let peerId = view.peerId var rightNavigationButton: ItemListNavigationButton? @@ -776,7 +857,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, view: view, state: state, participants: admins), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, view: view, state: state, participants: admins, antiSpamEnabled: antiSpamEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index bef523364a..34bcce2f3c 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -109,7 +109,7 @@ private enum LogoutOptionsEntry: ItemListNodeEntry, Equatable { } } -private func logoutOptionsEntries(presentationData: PresentationData, canAddAccounts: Bool, hasPasscode: Bool, hasWallets: Bool) -> [LogoutOptionsEntry] { +private func logoutOptionsEntries(presentationData: PresentationData, canAddAccounts: Bool, hasPasscode: Bool) -> [LogoutOptionsEntry] { var entries: [LogoutOptionsEntry] = [] entries.append(.alternativeHeader(presentationData.theme, presentationData.strings.LogoutOptions_AlternativeOptionsSection)) if canAddAccounts { @@ -257,19 +257,12 @@ public func logoutOptionsController(context: AccountContext, navigationControlle ]) presentControllerImpl?(alertController, nil) }) - - #if ENABLE_WALLET - let hasWallets = context.hasWallets - #else - let hasWallets: Signal = .single(false) - #endif - + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, - context.sharedContext.accountManager.accessChallengeData(), - hasWallets + context.sharedContext.accountManager.accessChallengeData() ) - |> map { presentationData, accessChallengeData, hasWallets -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, accessChallengeData -> (ItemListControllerState, (ItemListNodeState, Any)) in let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { dismissImpl?() }) @@ -283,7 +276,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.LogoutOptions_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: logoutOptionsEntries(presentationData: presentationData, canAddAccounts: canAddAccounts, hasPasscode: hasPasscode, hasWallets: hasWallets), style: .blocks) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: logoutOptionsEntries(presentationData: presentationData, canAddAccounts: canAddAccounts, hasPasscode: hasPasscode), style: .blocks) return (controllerState, (listState, arguments)) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift index f13300bfb4..53f9c8287c 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift @@ -343,9 +343,9 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, UIScrollViewDe self.secretChatsSwitchNode.isOn = session.flags.contains(.acceptsSecretChats) self.incomingCallsSwitchNode.isOn = session.flags.contains(.acceptsIncomingCalls) - if !session.flags.contains(.passwordPending) { + if !session.flags.contains(.passwordPending) && session.apiId != 22 { hasIncomingCalls = true - if ![2040, 2496].contains(session.apiId) { + if ![2040, 2496].contains(session.apiId) { hasSecretChats = true } } diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index 9ecbfff22c..ff3cb6d2e6 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -41,8 +41,6 @@ extension SettingsSearchableItemIcon { return PresentationResourcesSettings.watch case .passport: return PresentationResourcesSettings.passport - case .wallet: - return PresentationResourcesSettings.wallet case .support: return PresentationResourcesSettings.support case .faq: diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index c88e4a8361..227e41900e 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -33,7 +33,6 @@ enum SettingsSearchableItemIcon { case appearance case language case watch - case wallet case passport case support case faq @@ -56,7 +55,6 @@ public enum SettingsSearchableItemId: Hashable { case language(Int32) case watch(Int32) case passport(Int32) - case wallet(Int32) case support(Int32) case faq(Int32) case chatFolders(Int32) @@ -90,8 +88,6 @@ public enum SettingsSearchableItemId: Hashable { return 11 case .passport: return 12 - case .wallet: - return 13 case .support: return 14 case .faq: @@ -121,7 +117,6 @@ public enum SettingsSearchableItemId: Hashable { let .language(id), let .watch(id), let .passport(id), - let .wallet(id), let .support(id), let .faq(id), let .chatFolders(id), @@ -164,8 +159,6 @@ public enum SettingsSearchableItemId: Hashable { self = .watch(id) case 12: self = .passport(id) - case 13: - self = .wallet(id) case 14: self = .support(id) case 15: diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index ca9981ebdc..18aac8cd6f 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -219,7 +219,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView 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 }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in }, openForumThread: { _, _ in }) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index a3f0f6395b..d7c75c3d76 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -839,7 +839,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate 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 }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 0e5e02e2a4..bc9f227c93 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -363,7 +363,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { 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 }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index b5da27157e..7d23b54086 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -89,7 +89,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months)) case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId): return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId)) - case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, _): + case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, hidden): var components: [TelegramMediaActionType.ForumTopicEditComponent] = [] if let title = title { components.append(.title(title)) @@ -100,6 +100,9 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe if let closed = closed { components.append(.isClosed(closed == .boolTrue)) } + if let hidden = hidden { + components.append(.isHidden(hidden == .boolTrue)) + } return TelegramMediaAction(action: .topicEdited(components: components)) } } diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index a007a94b92..1e9044dae7 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -65,6 +65,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { case maxKnownMessageId case maxOutgoingReadId case isClosed + case isHidden case notificationSettings } @@ -77,6 +78,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { public var maxKnownMessageId: Int32 public var maxOutgoingReadId: Int32 public var isClosed: Bool + public var isHidden: Bool public var notificationSettings: TelegramPeerNotificationSettings public init( @@ -89,6 +91,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { maxKnownMessageId: Int32, maxOutgoingReadId: Int32, isClosed: Bool, + isHidden: Bool, notificationSettings: TelegramPeerNotificationSettings ) { self.creationDate = creationDate @@ -100,6 +103,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { self.maxKnownMessageId = maxKnownMessageId self.maxOutgoingReadId = maxOutgoingReadId self.isClosed = isClosed + self.isHidden = isHidden self.notificationSettings = notificationSettings } @@ -115,6 +119,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { self.maxKnownMessageId = try container.decode(Int32.self, forKey: .maxKnownMessageId) self.maxOutgoingReadId = try container.decode(Int32.self, forKey: .maxOutgoingReadId) self.isClosed = try container.decodeIfPresent(Bool.self, forKey: .isClosed) ?? false + self.isHidden = try container.decodeIfPresent(Bool.self, forKey: .isHidden) ?? false self.notificationSettings = try container.decode(TelegramPeerNotificationSettings.self, forKey: .notificationSettings) } @@ -130,6 +135,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { try container.encode(self.maxKnownMessageId, forKey: .maxKnownMessageId) try container.encode(self.maxOutgoingReadId, forKey: .maxOutgoingReadId) try container.encode(self.isClosed, forKey: .isClosed) + try container.encode(self.isHidden, forKey: .isHidden) try container.encode(self.notificationSettings, forKey: .notificationSettings) } } @@ -285,14 +291,16 @@ func _internal_editForumChannelTopic(account: Account, peerId: PeerId, threadId: } var flags: Int32 = 0 flags |= (1 << 0) - flags |= (1 << 1) + if threadId != 1 { + flags |= (1 << 1) + } return account.network.request(Api.functions.channels.editForumTopic( flags: flags, channel: inputChannel, topicId: Int32(clamping: threadId), title: title, - iconEmojiId: iconFileId ?? 0, + iconEmojiId: threadId == 1 ? nil : iconFileId ?? 0, closed: nil, hidden: nil )) @@ -367,6 +375,55 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t } } +func _internal_setForumChannelTopicHidden(account: Account, id: EnginePeer.Id, threadId: Int64, isHidden: Bool) -> Signal { + guard threadId == 1 else { + return .fail(.generic) + } + return account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(id).flatMap(apiInputChannel) + } + |> castError(EditForumChannelTopicError.self) + |> mapToSignal { inputChannel -> Signal in + guard let inputChannel = inputChannel else { + return .fail(.generic) + } + var flags: Int32 = 0 + flags |= (1 << 3) + + return account.network.request(Api.functions.channels.editForumTopic( + flags: flags, + channel: inputChannel, + topicId: Int32(clamping: threadId), + title: nil, + iconEmojiId: nil, + closed: nil, + hidden: isHidden ? .boolTrue : .boolFalse + )) + |> mapError { _ -> EditForumChannelTopicError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return account.postbox.transaction { transaction -> Void in + if let initialData = transaction.getMessageHistoryThreadInfo(peerId: id, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { + var data = initialData + + data.isHidden = isHidden + + if data != initialData { + if let entry = StoredMessageHistoryThreadInfo(data) { + transaction.setMessageHistoryThreadInfo(peerId: id, threadId: threadId, info: entry) + } + } + } + } + |> castError(EditForumChannelTopicError.self) + |> ignoreValues + } + } +} + public enum SetForumChannelTopicPinnedError { case generic case limitReached(Int) @@ -557,6 +614,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, isClosed: (flags & (1 << 2)) != 0, + isHidden: (flags & (1 << 6)) != 0, notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index f7133dc4ec..ec67ed0068 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1760,6 +1760,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, state: AccountMutab maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, isClosed: (flags & (1 << 2)) != 0, + isHidden: (flags & (1 << 6)) != 0, notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ), topMessageId: topMessage, @@ -1859,6 +1860,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, ids: [MessageId]) - maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, isClosed: (flags & (1 << 2)) != 0, + isHidden: (flags & (1 << 6)) != 0, notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ) if let entry = StoredMessageHistoryThreadInfo(data) { @@ -1982,6 +1984,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, isClosed: (flags & (1 << 2)) != 0, + isHidden: (flags & (1 << 6)) != 0, notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ), topMessageId: topMessage, @@ -3195,6 +3198,8 @@ func replayFinalState( data.info = EngineMessageHistoryThread.Info(title: data.info.title, icon: fileId == 0 ? nil : fileId, iconColor: data.info.iconColor) case let .isClosed(isClosed): data.isClosed = isClosed + case let .isHidden(isHidden): + data.isHidden = isHidden } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index 35c772d39c..b06ba78e42 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -18,6 +18,7 @@ public struct CachedChannelFlags: OptionSet { public static let canViewStats = CachedChannelFlags(rawValue: 1 << 4) public static let canChangePeerGeoLocation = CachedChannelFlags(rawValue: 1 << 5) public static let canDeleteHistory = CachedChannelFlags(rawValue: 1 << 6) + public static let antiSpamEnabled = CachedChannelFlags(rawValue: 1 << 7) } public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index c28f3cb6f6..9f28712874 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -239,7 +239,6 @@ private enum PreferencesKeyValues: Int32 { case appConfiguration = 14 case searchBotsConfiguration = 15 case contactsSettings = 16 - case walletCollection = 18 case contentSettings = 19 case chatListFilters = 20 case peersNearby = 21 @@ -339,13 +338,7 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.secretChatSettings.rawValue) return key }() - - public static let walletCollection: ValueBoxKey = { - let key = ValueBoxKey(length: 4) - key.setInt32(0, value: PreferencesKeyValues.walletCollection.rawValue) - return key - }() - + public static let contentSettings: ValueBoxKey = { let key = ValueBoxKey(length: 4) key.setInt32(0, value: PreferencesKeyValues.contentSettings.rawValue) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 79797e384d..b247aeb851 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -28,6 +28,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case title(String) case iconFileId(Int64?) case isClosed(Bool) + case isHidden(Bool) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("_t", orElse: 0) { @@ -37,6 +38,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .iconFileId(decoder.decodeOptionalInt64ForKey("fileId")) case 2: self = .isClosed(decoder.decodeBoolForKey("isClosed", orElse: false)) + case 3: + self = .isHidden(decoder.decodeBoolForKey("isHidden", orElse: false)) default: assertionFailure() self = .title("") @@ -58,6 +61,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case let .isClosed(isClosed): encoder.encodeInt32(2, forKey: "_t") encoder.encodeBool(isClosed, forKey: "isClosed") + case let .isHidden(isHidden): + encoder.encodeInt32(3, forKey: "_t") + encoder.encodeBool(isHidden, forKey: "isHidden") } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 85d93b4a32..82ef129524 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -806,6 +806,34 @@ public extension TelegramEngine.EngineData.Item { } } + public struct AntiSpamEnabled: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public typealias Result = Bool + + fileprivate var id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + var key: PostboxViewKey { + return .cachedPeerData(peerId: self.id) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? CachedPeerDataView else { + preconditionFailure() + } + if let cachedData = view.cachedPeerData as? CachedChannelData { + return cachedData.flags.contains(.antiSpamEnabled) + } else { + return false + } + } + } + public struct LegacyGroupParticipants: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = EnginePeerCachedInfoItem<[EngineLegacyGroupParticipant]> diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AntiSpam.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AntiSpam.swift new file mode 100644 index 0000000000..3c49d3b24f --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AntiSpam.swift @@ -0,0 +1,38 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + +func _internal_toggleAntiSpamProtection(account: Account, peerId: PeerId, enabled: Bool) -> Signal { + return account.postbox.transaction { transaction -> Signal in + if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.toggleAntiSpam(channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse)) |> `catch` { _ in .complete() } |> map { updates -> Void in + account.stateManager.addUpdates(updates) + } + } else { + return .complete() + } + } |> switchToLatest +} + +func _internal_reportAntiSpamFalsePositive(account: Account, peerId: PeerId, messageId: MessageId) -> Signal { + return account.postbox.transaction { transaction -> Signal in + if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.reportAntiSpamFalsePositive(channel: inputChannel, msgId: messageId.id)) + |> map { result -> Bool in + switch result { + case .boolTrue: + return true + case .boolFalse: + return false + } + } + |> `catch` { _ -> Signal in + return .single(false) + } + } else { + return .complete() + } + } |> switchToLatest +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index e3d549f2f5..3e6c12fed7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -287,6 +287,10 @@ public extension TelegramEngine { public func toggleChannelJoinRequest(peerId: PeerId, enabled: Bool) -> Signal { return _internal_toggleChannelJoinRequest(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, enabled: enabled) } + + public func toggleAntiSpamProtection(peerId: PeerId, enabled: Bool) -> Signal { + return _internal_toggleAntiSpamProtection(account: self.account, peerId: peerId, enabled: enabled) + } public func requestPeerPhotos(peerId: PeerId) -> Signal<[TelegramPeerPhoto], NoError> { return _internal_requestPeerPhotos(postbox: self.account.postbox, network: self.account.network, peerId: peerId) @@ -847,6 +851,10 @@ public extension TelegramEngine { return _internal_setForumChannelTopicClosed(account: self.account, id: id, threadId: threadId, isClosed: isClosed) } + public func setForumChannelTopicHidden(id: EnginePeer.Id, threadId: Int64, isHidden: Bool) -> Signal { + return _internal_setForumChannelTopicHidden(account: self.account, id: id, threadId: threadId, isHidden: isHidden) + } + public func removeForumChannelThread(id: EnginePeer.Id, threadId: Int64) -> Signal { return self.account.postbox.transaction { transaction -> Void in cloudChatAddClearHistoryOperation(transaction: transaction, peerId: id, threadId: threadId, explicitTopMessageId: nil, minTimestamp: nil, maxTimestamp: nil, type: CloudChatClearHistoryType(.forEveryone)) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 9150d78dfb..55d53d9e55 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -458,6 +458,9 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee if (flags2 & (1 << 0)) != 0 { channelFlags.insert(.canDeleteHistory) } + if (flags2 & Int32(1 << 1)) != 0 { + channelFlags.insert(.antiSpamEnabled) + } let sendAsPeerId = defaultSendAs?.peerId diff --git a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift index 47d99d047d..27c48e6034 100644 --- a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift @@ -2,8 +2,6 @@ import Foundation import Postbox public extension Peer { - - var debugDisplayTitle: String { switch self { case let user as TelegramUser: diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index ec8798ec64..33268f09cc 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -105,6 +105,9 @@ public enum PresentationResourceKey: Int32 { case chatListRecentStatusVoiceChatHighlightedIcon case chatListRecentStatusVoiceChatPinnedIcon case chatListRecentStatusVoiceChatPanelIcon + + case chatListGeneralTopicIcon + case chatListGeneralTopicSmallIcon case chatTitleLockIcon case chatTitleMuteIcon @@ -285,6 +288,8 @@ public enum PresentationResourceKey: Int32 { case chatKeyboardActionButtonAddToChatIcon case chatKeyboardActionButtonWebAppIcon + case chatGeneralThreadIcon + case uploadToneIcon } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index e5df3d2385..f377670c0e 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -212,7 +212,7 @@ public struct PresentationResourcesChat { })?.stretchableImage(withLeftCapWidth: 1, topCapHeight: 4) }) } - + public static func chatInfoItemBackgroundImageWithoutWallpaper(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInfoItemBackgroundImageWithoutWallpaper.rawValue, { theme in return messageSingleBubbleLikeImage(fillColor: theme.chat.message.incoming.bubble.withoutWallpaper.fill[0], strokeColor: theme.chat.message.incoming.bubble.withoutWallpaper.stroke) @@ -344,7 +344,7 @@ public struct PresentationResourcesChat { ] var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0] let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! - + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions()) } }) @@ -420,7 +420,7 @@ public struct PresentationResourcesChat { return generateInputPanelButtonStrokeImage(color: theme.chat.inputButtonPanel.buttonStrokeColor, offset: 1.0) }) } - + public static func chatInputTextFieldBackgroundImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInputTextFieldBackgroundImage.rawValue, { theme in let diameter: CGFloat = 35.0 @@ -428,7 +428,7 @@ public struct PresentationResourcesChat { let context = UIGraphicsGetCurrentContext()! context.setFillColor(theme.chat.inputPanel.panelBackgroundColor.cgColor) context.fill(CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)) - + context.setBlendMode(.clear) context.setFillColor(UIColor.clear.cgColor) context.fillEllipse(in: CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)) @@ -1053,7 +1053,7 @@ public struct PresentationResourcesChat { }) }) } - + public static func chatBubbleFileCloudFetchMediaIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatBubbleFileCloudFetchMediaIcon.rawValue, { theme in guard let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: theme.chat.message.mediaOverlayControlColors.foregroundColor) else { @@ -1247,13 +1247,13 @@ public struct PresentationResourcesChat { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FreeRepliesIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) }) } - + public static func chatFreeNavigateButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { return theme.image(PresentationResourceKey.chatFreeNavigateButtonIcon.rawValue, { _ in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/NavigateToMessageIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) }) } - + public static func chatFreeShareButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { return theme.image(PresentationResourceKey.chatFreeShareButtonIcon.rawValue, { _ in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ShareIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) @@ -1319,4 +1319,10 @@ public struct PresentationResourcesChat { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelSectionLockIcon"), color: color) }) } + + public static func chatGeneralThreadIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatGeneralThreadIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Info/GeneralIcon"), color: theme.rootController.navigationBar.controlColor) + }) + } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift index e542dad0f4..4c314fc4f5 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift @@ -384,4 +384,22 @@ public struct PresentationResourcesChatList { return generateTintedImage(image: UIImage(bundleImageName: "Chat List/TopicArrowIcon"), color: theme.chatList.titleColor) }) } + + public static func generalTopicSmallIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListGeneralTopicSmallIcon.rawValue, { theme in + let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor) + return generateImage(CGSize(width: 18.0, height: 18.0), contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + if let cgImage = image?.cgImage { + context.draw(cgImage, in: CGRect(origin: .zero, size: size)) + } + }) + }) + } + + public static func generalTopicIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListGeneralTopicIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor) + }) + } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift index 4f0e6df3a5..b6f7954f87 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift @@ -37,20 +37,6 @@ public struct PresentationResourcesSettings { public static let language = renderIcon(name: "Settings/Menu/Language") public static let deleteAccount = renderIcon(name: "Chat/Info/GroupRemovedIcon") - - public static let wallet = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in - let bounds = CGRect(origin: CGPoint(), size: size) - context.clear(bounds) - - context.setFillColor(UIColor.white.cgColor) - context.fill(bounds.insetBy(dx: 5.0, dy: 5.0)) - - if let image = generateTintedImage(image: UIImage(bundleImageName: "Settings/Menu/Wallet"), color: UIColor(rgb: 0x1b1b1c))?.cgImage { - context.draw(image, in: bounds) - } - - drawBorder(context: context, rect: bounds) - }) public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 1fe65c511c..d741df560c 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -682,12 +682,49 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, case let .topicCreated(title, iconColor, iconFileId): if forForumOverview { let maybeFileId = iconFileId ?? 0 - attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicCreated(".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicCreated(".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } else { attributedString = NSAttributedString(string: strings.Notification_ForumTopicCreated, font: titleFont, textColor: primaryTextColor) } case let .topicEdited(components): - if let isClosed = components.compactMap({ item -> Bool? in + if let isHidden = components.compactMap({ item -> Bool? in + switch item { + case let .isHidden(isHidden): + return isHidden + default: + return nil + } + }).first { + if case let .user(user) = message.author { + if forForumOverview { + var title: String = "" + var iconColor: Int32 = 0 + var maybeFileId: Int64 = 0 + if let info = message.associatedThreadInfo { + iconColor = info.iconColor + title = info.title + maybeFileId = info.icon ?? 0 + } + if isHidden { + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicHidden(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + } else { + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicUnhidden(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + } + } else { + if isHidden { + attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicHiddenAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id)]) + } else { + attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicUnhiddenAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id)]) + } + } + } else { + if isHidden { + attributedString = NSAttributedString(string: strings.Notification_ForumTopicHidden, font: titleFont, textColor: primaryTextColor) + } else { + attributedString = NSAttributedString(string: strings.Notification_ForumTopicUnhidden, font: titleFont, textColor: primaryTextColor) + } + } + } else if let isClosed = components.compactMap({ item -> Bool? in switch item { case let .isClosed(isClosed): return isClosed @@ -706,9 +743,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, maybeFileId = info.icon ?? 0 } if isClosed { - attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicClosed(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicClosed(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } else { - attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicReopened(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicReopened(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } } else { if isClosed { @@ -746,7 +783,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicRenamedIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [ 0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), - 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)]) + 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)]) ]) } else { attributedString = NSAttributedString(string: strings.Notification_ForumTopicRenamed(title).string, font: titleFont, textColor: primaryTextColor) @@ -779,9 +816,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, title = info.title } if case let .user(user) = message.author { - attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".")._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".")._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } else { - attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChanged(".")._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChanged(".")._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } } case .unknown: diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index db68f8935d..1f962744bf 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -49,6 +49,7 @@ public final class EmojiStatusComponent: Component { case text(color: UIColor, string: String) case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode) case topic(title: String, color: Int32, size: CGSize) + case image(image: UIImage?) } public let context: AccountContext @@ -230,6 +231,8 @@ public final class EmojiStatusComponent: Component { } else { iconImage = nil } + case let .image(image): + iconImage = image case let .verified(fillColor, foregroundColor, sizeType): let imageNamePrefix: String switch sizeType { diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index 4c748f9fca..adcf94e737 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -261,23 +261,33 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } } - private func updateTopicInfo(topicInfo: EngineMessageHistoryThread.Info) { - func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) { - return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]) - } - - let topicColors: [Int32: ([UInt32], [UInt32])] = [ - 0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]), - 0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]), - 0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]), - 0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]), - 0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]), - 0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506]) - ] - let colors = topicColors[topicInfo.iconColor] ?? generateTopicColors(topicInfo.iconColor) - - if let image = generateTopicIcon(title: String(topicInfo.title.prefix(1)), backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: CGSize(width: 32.0, height: 32.0)) { - self.contents = image.cgImage + private func updateTopicInfo(topicInfo: (Int64, EngineMessageHistoryThread.Info)) { + if topicInfo.0 == 1 { + let image = generateImage(CGSize(width: 18.0, height: 18.0), contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + if let cgImage = generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: .white)?.cgImage { + context.draw(cgImage, in: CGRect(origin: .zero, size: size)) + } + }) + self.contents = image?.cgImage + } else { + func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) { + return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]) + } + + let topicColors: [Int32: ([UInt32], [UInt32])] = [ + 0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]), + 0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]), + 0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]), + 0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]), + 0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]), + 0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506]) + ] + let colors = topicColors[topicInfo.1.iconColor] ?? generateTopicColors(topicInfo.1.iconColor) + + if let image = generateTopicIcon(title: String(topicInfo.1.title.prefix(1)), backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: CGSize(width: 32.0, height: 32.0)) { + self.contents = image.cgImage + } } } diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index b8349516ac..d127f5eb98 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -24,6 +24,7 @@ private final class TitleFieldComponent: Component { let textColor: UIColor let accentColor: UIColor let placeholderColor: UIColor + let isGeneral: Bool let fileId: Int64 let iconColor: Int32 let text: String @@ -36,6 +37,7 @@ private final class TitleFieldComponent: Component { textColor: UIColor, accentColor: UIColor, placeholderColor: UIColor, + isGeneral: Bool, fileId: Int64, iconColor: Int32, text: String, @@ -47,6 +49,7 @@ private final class TitleFieldComponent: Component { self.textColor = textColor self.accentColor = accentColor self.placeholderColor = placeholderColor + self.isGeneral = isGeneral self.fileId = fileId self.iconColor = iconColor self.text = text @@ -68,6 +71,9 @@ private final class TitleFieldComponent: Component { if lhs.placeholderColor != rhs.placeholderColor { return false } + if lhs.isGeneral != rhs.isGeneral { + return false + } if lhs.fileId != rhs.fileId { return false } @@ -140,7 +146,10 @@ private final class TitleFieldComponent: Component { self.state = state let iconContent: EmojiStatusComponent.Content - if component.fileId == 0 { + if component.isGeneral { + iconContent = .image(image: generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: component.placeholderColor)) + self.iconButton.isUserInteractionEnabled = false + } else if component.fileId == 0 { iconContent = .topic(title: String(component.text.prefix(1)), color: component.iconColor, size: CGSize(width: 32.0, height: 32.0)) self.iconButton.isUserInteractionEnabled = true } else { @@ -413,6 +422,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { private var defaultIconFilesDisposable: Disposable? private var defaultIconFiles = Set() + let isGeneral: Bool var title: String var fileId: Int64 var iconColor: Int32 @@ -427,11 +437,13 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { switch mode { case .create: + self.isGeneral = false self.title = "" self.fileId = 0 self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0 - case let .edit(info): + case let .edit(threadId, info): + self.isGeneral = threadId == 1 self.title = info.title self.fileId = info.icon ?? 0 self.iconColor = info.iconColor @@ -638,6 +650,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { textColor: environment.theme.list.itemPrimaryTextColor, accentColor: environment.theme.list.itemAccentColor, placeholderColor: environment.theme.list.disclosureArrowColor, + isGeneral: state.isGeneral, fileId: state.fileId, iconColor: state.iconColor, text: state.title, @@ -658,124 +671,128 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { contentHeight += titleBackground.size.height + sectionSpacing - let iconHeader = iconHeader.update( - component: MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.CreateTopic_SelectTopicIcon, - font: Font.regular(13.0), - textColor: environment.theme.list.freeTextColor, - paragraphAlignment: .natural) - ), - horizontalAlignment: .natural, - maximumNumberOfLines: 1 - ), - availableSize: CGSize( - width: context.availableSize.width - sideInset * 2.0, - height: CGFloat.greatestFiniteMagnitude - ), - transition: .immediate - ) - context.add(iconHeader - .position(CGPoint(x: sideInset * 2.0 + iconHeader.size.width / 2.0, y: contentHeight + iconHeader.size.height / 2.0)) - ) - contentHeight += iconHeader.size.height + headerSpacing - - let bottomInset = max(environment.safeInsets.bottom, 12.0) - - let iconBackground = iconBackground.update( - component: RoundedRectangle( - color: environment.theme.list.itemBlocksBackgroundColor, - cornerRadius: 10.0 - ), - availableSize: CGSize( - width: context.availableSize.width - sideInset * 2.0, - height: context.availableSize.height - contentHeight - bottomInset - ), - transition: context.transition - ) - context.add(iconBackground - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconBackground.size.height / 2.0)) - ) - - if let emojiContent = state.emojiContent { - let availableHeight = context.availableSize.height - contentHeight - max(bottomInset, environment.inputHeight) + if case let .edit(threadId, _) = context.component.mode, threadId == 1 { - let iconSelector = iconSelector.update( - component: TopicIconSelectionComponent( - theme: environment.theme, - strings: environment.strings, - deviceMetrics: environment.deviceMetrics, - emojiContent: emojiContent, - backgroundColor: environment.theme.list.itemBlocksBackgroundColor, - separatorColor: environment.theme.list.blocksBackgroundColor + } else { + let iconHeader = iconHeader.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.CreateTopic_SelectTopicIcon, + font: Font.regular(13.0), + textColor: environment.theme.list.freeTextColor, + paragraphAlignment: .natural) + ), + horizontalAlignment: .natural, + maximumNumberOfLines: 1 + ), + availableSize: CGSize( + width: context.availableSize.width - sideInset * 2.0, + height: CGFloat.greatestFiniteMagnitude + ), + transition: .immediate + ) + context.add(iconHeader + .position(CGPoint(x: sideInset * 2.0 + iconHeader.size.width / 2.0, y: contentHeight + iconHeader.size.height / 2.0)) + ) + contentHeight += iconHeader.size.height + headerSpacing + + let bottomInset = max(environment.safeInsets.bottom, 12.0) + + let iconBackground = iconBackground.update( + component: RoundedRectangle( + color: environment.theme.list.itemBlocksBackgroundColor, + cornerRadius: 10.0 + ), + availableSize: CGSize( + width: context.availableSize.width - sideInset * 2.0, + height: context.availableSize.height - contentHeight - bottomInset ), - environment: {}, - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: availableHeight), transition: context.transition ) - context.add(iconSelector - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconSelector.size.height / 2.0)) - .cornerRadius(10.0) - .clipsToBounds(true) + context.add(iconBackground + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconBackground.size.height / 2.0)) ) - let accountContext = context.component.context - emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( - performItemAction: { [weak state] groupId, item, _, _, _, _ in - state?.applyItem(groupId: groupId, item: item) - }, - deleteBackwards: { - }, - openStickerSettings: { - }, - openFeatured: { - }, - addGroupAction: { groupId, isPremiumLocked in - guard let collectionId = groupId.base as? ItemCollectionId else { - return - } - - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (accountContext.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { + if let emojiContent = state.emojiContent { + let availableHeight = context.availableSize.height - contentHeight - max(bottomInset, environment.inputHeight) + + let iconSelector = iconSelector.update( + component: TopicIconSelectionComponent( + theme: environment.theme, + strings: environment.strings, + deviceMetrics: environment.deviceMetrics, + emojiContent: emojiContent, + backgroundColor: environment.theme.list.itemBlocksBackgroundColor, + separatorColor: environment.theme.list.blocksBackgroundColor + ), + environment: {}, + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: availableHeight), + transition: context.transition + ) + context.add(iconSelector + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconSelector.size.height / 2.0)) + .cornerRadius(10.0) + .clipsToBounds(true) + ) + + let accountContext = context.component.context + emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( + performItemAction: { [weak state] groupId, item, _, _, _, _ in + state?.applyItem(groupId: groupId, item: item) + }, + deleteBackwards: { + }, + openStickerSettings: { + }, + openFeatured: { + }, + addGroupAction: { groupId, isPremiumLocked in + guard let collectionId = groupId.base as? ItemCollectionId else { return } - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { - if featuredEmojiPack.info.id == collectionId { -// if let strongSelf = self { -// strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId)) -// } - let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start() - - break + + let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) + let _ = (accountContext.account.postbox.combinedView(keys: [viewKey]) + |> take(1) + |> deliverOnMainQueue).start(next: { views in + guard let view = views.views[viewKey] as? OrderedItemListView else { + return } - } - }) - }, - clearGroup: { _ in - }, - pushController: { c in - }, - presentController: { c in - }, - presentGlobalOverlayController: { c in - }, - navigationController: { - return nil - }, - requestUpdate: { _ in - }, - updateSearchQuery: { _, _ in - }, - chatPeerId: nil, - peekBehavior: nil, - customLayout: nil, - externalBackground: nil, - externalExpansionView: nil, - useOpaqueTheme: true - ) + for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + if featuredEmojiPack.info.id == collectionId { + // if let strongSelf = self { + // strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId)) + // } + let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start() + + break + } + } + }) + }, + clearGroup: { _ in + }, + pushController: { c in + }, + presentController: { c in + }, + presentGlobalOverlayController: { c in + }, + navigationController: { + return nil + }, + requestUpdate: { _ in + }, + updateSearchQuery: { _, _ in + }, + chatPeerId: nil, + peekBehavior: nil, + customLayout: nil, + externalBackground: nil, + externalExpansionView: nil, + useOpaqueTheme: true + ) + } } return context.availableSize @@ -788,7 +805,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer { public enum Mode: Equatable { case create - case edit(topic: EngineMessageHistoryThread.Info) + case edit(threadId: Int64, threadInfo: EngineMessageHistoryThread.Info) } private let context: AccountContext @@ -835,9 +852,9 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer { case .create: title = presentationData.strings.CreateTopic_CreateTitle doneTitle = presentationData.strings.CreateTopic_Create - case let .edit(topic): + case let .edit(_, topic): title = presentationData.strings.CreateTopic_EditTitle - doneTitle = presentationData.strings.Common_Done + doneTitle = presentationData.strings.Common_Done self.state = (topic.title, topic.icon) } diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/Contents.json new file mode 100644 index 0000000000..a696d6f8ef --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_general.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/ic_general.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/ic_general.pdf new file mode 100644 index 0000000000..0718bbc832 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/GeneralTopicIcon.imageset/ic_general.pdf @@ -0,0 +1,113 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.799988 1.991699 cm +0.000000 0.000000 0.000000 scn +4.025391 7.171875 m +3.005859 2.056641 l +2.953125 1.880859 2.935547 1.634766 2.935547 1.458984 c +2.935547 0.544922 3.533203 0.000000 4.482422 0.000000 c +5.484375 0.000000 6.082031 0.509766 6.292969 1.582031 c +7.417969 7.171875 l +12.515625 7.171875 l +11.478516 2.056641 l +11.425781 1.880859 11.408203 1.634766 11.408203 1.458984 c +11.408203 0.544922 12.005859 0.000000 12.955078 0.000000 c +13.957031 0.000000 14.554688 0.544922 14.765625 1.617188 c +15.908203 7.171875 l +18.931641 7.171875 l +20.074219 7.171875 20.812500 7.927734 20.812500 9.035156 c +20.812500 9.914062 20.232422 10.511719 19.300781 10.511719 c +16.558594 10.511719 l +17.648438 15.855469 l +20.566406 15.855469 l +21.708984 15.855469 22.447266 16.593750 22.447266 17.701172 c +22.447266 18.580078 21.867188 19.160156 20.935547 19.160156 c +18.316406 19.160156 l +19.300781 24.011719 l +19.353516 24.187500 19.371094 24.433594 19.371094 24.609375 c +19.371094 25.523438 18.773438 26.068359 17.824219 26.068359 c +16.822266 26.068359 16.224609 25.523438 16.013672 24.451172 c +14.923828 19.160156 l +9.861328 19.160156 l +10.828125 24.011719 l +10.863281 24.187500 10.898438 24.433594 10.898438 24.609375 c +10.898438 25.523438 10.300781 26.068359 9.351562 26.068359 c +8.349609 26.068359 7.751953 25.523438 7.541016 24.468750 c +6.433594 19.160156 l +3.498047 19.160156 l +2.355469 19.160156 1.599609 18.421875 1.599609 17.314453 c +1.599609 16.453125 2.197266 15.837891 3.128906 15.837891 c +5.783203 15.837891 l +4.710938 10.494141 l +1.898438 10.494141 l +0.738281 10.494141 0.000000 9.755859 0.000000 8.648438 c +0.000000 7.787109 0.597656 7.171875 1.529297 7.171875 c +4.025391 7.171875 l +h +7.875000 10.283203 m +9.052734 16.066406 l +14.466797 16.066406 l +13.271484 10.283203 l +7.875000 10.283203 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 1868 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001958 00000 n +0000001981 00000 n +0000002154 00000 n +0000002228 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2287 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/AntiSpamTooltip.pdf b/submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/AntiSpamTooltip.pdf new file mode 100644 index 0000000000..b8af1388d8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/AntiSpamTooltip.pdf @@ -0,0 +1,112 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.199997 1.907497 cm +1.000000 1.000000 1.000000 scn +0.000000 12.592503 m +0.000000 18.498425 l +0.000000 19.400753 0.000000 19.851917 0.142046 20.246223 c +0.267605 20.594761 0.472146 20.909500 0.739650 21.165794 c +1.042280 21.455738 1.454996 21.639168 2.280427 22.006027 c +7.680857 24.406218 l +8.829472 24.916712 9.403779 25.171961 10.000750 25.272770 c +10.529839 25.362116 11.070163 25.362116 11.599251 25.272770 c +12.196222 25.171961 12.770529 24.916714 13.919143 24.406218 c +19.319572 22.006027 l +20.145004 21.639168 20.557720 21.455738 20.860350 21.165794 c +21.127855 20.909500 21.332396 20.594761 21.457954 20.246223 c +21.600000 19.851917 21.600000 19.400753 21.600000 18.498423 c +21.600000 12.592503 l +21.600000 4.965950 14.783872 1.126137 11.981524 -0.130262 c +11.981509 -0.130268 l +11.662810 -0.273155 11.503456 -0.344597 11.202699 -0.395788 c +10.999416 -0.430387 10.600584 -0.430387 10.397303 -0.395788 c +10.096541 -0.344597 9.937186 -0.273151 9.618476 -0.130262 c +6.816126 1.126139 0.000000 4.965952 0.000000 12.592503 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 7.199997 8.199517 cm +0.274510 0.294118 0.298039 scn +0.989925 6.863103 m +4.855387 8.547224 7.432969 9.657499 8.722668 10.193930 c +12.405018 11.725546 13.170178 11.991605 13.668900 12.000390 c +13.778588 12.002322 14.023846 11.975138 14.182714 11.846229 c +14.316857 11.737380 14.353765 11.590341 14.371427 11.487141 c +14.389089 11.383940 14.411081 11.148846 14.393599 10.965151 c +14.194051 8.868484 13.330610 3.780426 12.891341 1.432123 c +12.705469 0.438469 12.339483 0.105302 11.985166 0.072697 c +11.215152 0.001839 10.630439 0.581574 9.884643 1.070453 c +8.717619 1.835451 8.058326 2.311666 6.925527 3.058163 c +5.616383 3.920870 6.465046 4.395029 7.211124 5.169937 c +7.406376 5.372734 10.799074 8.458654 10.864739 8.738596 c +10.872952 8.773607 10.880574 8.904113 10.803043 8.973024 c +10.725513 9.041937 10.611083 9.018372 10.528507 8.999630 c +10.411459 8.973064 8.547124 7.740808 4.935502 5.302861 c +4.406317 4.939481 3.926997 4.762432 3.497544 4.771710 c +3.024105 4.781938 2.113399 5.039400 1.436383 5.259471 c +0.605995 5.529397 -0.053981 5.672108 0.003489 6.130527 c +0.033422 6.369299 0.362234 6.613492 0.989925 6.863103 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2275 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002365 00000 n +0000002388 00000 n +0000002561 00000 n +0000002635 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2694 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/Contents.json new file mode 100644 index 0000000000..c2e19c2189 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/AntiSpamTooltipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "AntiSpamTooltip.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Antispam.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Antispam.pdf new file mode 100644 index 0000000000..f868792da8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Antispam.pdf @@ -0,0 +1,135 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.203922 0.780392 0.349020 scn +0.000000 18.799999 m +0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c +1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c +5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c +18.799999 30.000000 l +22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c +27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c +30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c +30.000000 11.200001 l +30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c +28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c +24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c +11.200000 0.000000 l +7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c +2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c +0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c +0.000000 18.799999 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 6.000000 4.506248 cm +1.000000 1.000000 1.000000 scn +0.000000 10.493752 m +0.000000 15.415352 l +0.000000 16.167294 0.000000 16.543264 0.118372 16.871851 c +0.223004 17.162300 0.393455 17.424583 0.616375 17.638161 c +0.868567 17.879782 1.212497 18.032639 1.900359 18.338356 c +6.400713 20.338512 l +7.357893 20.763926 7.836482 20.976633 8.333958 21.060640 c +8.774865 21.135096 9.225135 21.135096 9.666042 21.060640 c +10.163518 20.976633 10.642108 20.763926 11.599288 20.338512 c +16.099644 18.338354 l +16.787502 18.032639 17.131433 17.879782 17.383625 17.638161 c +17.606544 17.424583 17.776997 17.162300 17.881628 16.871851 c +18.000000 16.543264 18.000000 16.167294 18.000000 15.415352 c +18.000000 10.493752 l +18.000000 4.138292 12.319893 0.938446 9.984602 -0.108553 c +9.719011 -0.227627 9.586216 -0.287165 9.335582 -0.329823 c +9.166181 -0.358656 8.833819 -0.358656 8.664418 -0.329823 c +8.413784 -0.287165 8.280988 -0.227627 8.015397 -0.108553 c +5.680106 0.938448 0.000000 4.138292 0.000000 10.493752 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 8.500000 9.749598 cm +0.203922 0.780392 0.349020 scn +0.824937 5.719253 m +4.046156 7.122686 6.194141 8.047915 7.268889 8.494941 c +10.337516 9.771288 10.975148 9.993003 11.390750 10.000324 c +11.482157 10.001935 11.686539 9.979281 11.818928 9.871857 c +11.930715 9.781149 11.961472 9.658617 11.976190 9.572617 c +11.990908 9.486616 12.009235 9.290705 11.994666 9.137626 c +11.828376 7.390403 11.108842 3.150355 10.742785 1.193437 c +10.587892 0.365391 10.282903 0.087751 9.987638 0.060581 c +9.345960 0.001533 8.858699 0.484646 8.237203 0.892044 c +7.264683 1.529543 6.715271 1.926389 5.771273 2.548470 c +4.680319 3.267392 5.387539 3.662524 6.009270 4.308280 c +6.171980 4.477278 8.999228 7.048879 9.053950 7.282163 c +9.060794 7.311339 9.067145 7.420094 9.002536 7.477520 c +8.937927 7.534947 8.842569 7.515309 8.773756 7.499691 c +8.676216 7.477553 7.122603 6.450673 4.112918 4.419051 c +3.671931 4.116235 3.272498 3.968693 2.914620 3.976425 c +2.520088 3.984949 1.761166 4.199500 1.196986 4.382892 c +0.504996 4.607831 -0.044984 4.726757 0.002907 5.108772 c +0.027852 5.307749 0.301862 5.511243 0.824937 5.719253 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3134 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003224 00000 n +0000003247 00000 n +0000003420 00000 n +0000003494 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3553 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Contents.json new file mode 100644 index 0000000000..f925374d5a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Info/AntiSpam.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Antispam.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/Contents.json new file mode 100644 index 0000000000..4d60ce741e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_general (1).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/ic_general (1).pdf b/submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/ic_general (1).pdf new file mode 100644 index 0000000000..50c23a5a09 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Info/GeneralIcon.imageset/ic_general (1).pdf @@ -0,0 +1,113 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 12.666870 6.638916 cm +0.000000 0.000000 0.000000 scn +13.417969 23.906250 m +10.019531 6.855469 l +9.843750 6.269531 9.785156 5.449219 9.785156 4.863281 c +9.785156 1.816406 11.777344 0.000000 14.941406 0.000000 c +18.281250 0.000000 20.273438 1.699219 20.976562 5.273438 c +24.726562 23.906250 l +41.718750 23.906250 l +38.261719 6.855469 l +38.085938 6.269531 38.027344 5.449219 38.027344 4.863281 c +38.027344 1.816406 40.019531 0.000000 43.183594 0.000000 c +46.523438 0.000000 48.515625 1.816406 49.218750 5.390625 c +53.027344 23.906250 l +63.105469 23.906250 l +66.914062 23.906250 69.375000 26.425781 69.375000 30.117188 c +69.375000 33.046875 67.441406 35.039062 64.335938 35.039062 c +55.195312 35.039062 l +58.828125 52.851562 l +68.554688 52.851562 l +72.363281 52.851562 74.824219 55.312500 74.824219 59.003906 c +74.824219 61.933594 72.890625 63.867188 69.785156 63.867188 c +61.054688 63.867188 l +64.335938 80.039062 l +64.511719 80.625000 64.570312 81.445312 64.570312 82.031250 c +64.570312 85.078125 62.578125 86.894531 59.414062 86.894531 c +56.074219 86.894531 54.082031 85.078125 53.378906 81.503906 c +49.746094 63.867188 l +32.871094 63.867188 l +36.093750 80.039062 l +36.210938 80.625000 36.328125 81.445312 36.328125 82.031250 c +36.328125 85.078125 34.335938 86.894531 31.171875 86.894531 c +27.832031 86.894531 25.839844 85.078125 25.136719 81.562500 c +21.445312 63.867188 l +11.660156 63.867188 l +7.851562 63.867188 5.332031 61.406250 5.332031 57.714844 c +5.332031 54.843750 7.324219 52.792969 10.429688 52.792969 c +19.277344 52.792969 l +15.703125 34.980469 l +6.328125 34.980469 l +2.460938 34.980469 0.000000 32.519531 0.000000 28.828125 c +0.000000 25.957031 1.992188 23.906250 5.097656 23.906250 c +13.417969 23.906250 l +h +26.250000 34.277344 m +30.175781 53.554688 l +48.222656 53.554688 l +44.238281 34.277344 l +26.250000 34.277344 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 1906 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 100.000000 100.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001996 00000 n +0000002019 00000 n +0000002194 00000 n +0000002268 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2327 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 08118cd00f..c8f4c11eb7 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4949,7 +4949,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount) let avatarContent: EmojiStatusComponent.Content - if let fileId = threadInfo.icon { + if strongSelf.chatLocation.threadId == 1 { + avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme)) + } else if let fileId = threadInfo.icon { avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1)) } else { avatarContent = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: 32.0, height: 32.0)) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 4b75f4403a..f5a706d479 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -960,7 +960,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState var isLargeFile = false for media in message.media { if let file = media as? TelegramMediaFile { - if let size = file.size, size >= 300 * 1024 * 1024 { + if let size = file.size, size >= 150 * 1024 * 1024 { isLargeFile = true } break diff --git a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift index fd7f827865..f798f66cbe 100644 --- a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift @@ -335,9 +335,15 @@ class ChatMessageThreadInfoNode: ASDisplayNode { textColor = UIColor(rgb: colors.1.first ?? 0x000000) arrowIcon = PresentationResourcesChat.chatBubbleArrowImage(color: textColor.withAlphaComponent(0.3)) } else { - backgroundColor = (incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor) - textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor - arrowIcon = incoming ? PresentationResourcesChat.chatBubbleArrowIncomingImage(arguments.presentationData.theme.theme) : PresentationResourcesChat.chatBubbleArrowOutgoingImage(arguments.presentationData.theme.theme) + if incoming { + backgroundColor = arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor + textColor = arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor + arrowIcon = PresentationResourcesChat.chatBubbleArrowIncomingImage(arguments.presentationData.theme.theme) + } else { + backgroundColor = arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor + textColor = arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor + arrowIcon = PresentationResourcesChat.chatBubbleArrowOutgoingImage(arguments.presentationData.theme.theme) + } } case .standalone: textColor = .white diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 16fd355b29..56e3d67b97 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -87,6 +87,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { private var adminsState: ChannelMemberListState? private let banDisposables = DisposableDict() + private weak var antiSpamTooltipController: UndoOverlayController? + private weak var controller: ChatRecentActionsController? init(context: AccountContext, controller: ChatRecentActionsController, peer: Peer, presentationData: PresentationData, interaction: ChatRecentActionsInteraction, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, PresentationContextType, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?) { @@ -774,18 +776,33 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } private func openPeer(peer: EnginePeer, peekData: ChatPeekTimeout? = nil) { - let peerSignal: Signal = .single(peer._asPeer()) - self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, let peer = peer { - if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), peekData: peekData, animated: true)) - } else { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { - strongSelf.pushController(infoController) + let antiSpamBotConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + if peer.id == antiSpamBotConfiguration.antiSpamBotId { + self.dismissAllTooltips() + + self.presentController(UndoOverlayController(presentationData: self.presentationData, content: .image(image: UIImage(bundleImageName: "Chat/AntiSpamTooltipIcon")!, title: self.presentationData.strings.Group_AdminLog_AntiSpamTitle, text: self.presentationData.strings.Group_AdminLog_AntiSpamText, undo: false), elevatedLayout: true, action: { [weak self] action in + if let strongSelf = self { + if case .info = action { + let _ = strongSelf.getNavigationController()?.popViewController(animated: true) + return true } } - } - })) + return false + }), .window(.root), nil) + } else { + let peerSignal: Signal = .single(peer._asPeer()) + self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, let peer = peer { + if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), peekData: peekData, animated: true)) + } else { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + strongSelf.pushController(infoController) + } + } + } + })) + } } private func openPeerMention(_ name: String) { @@ -966,10 +983,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break case .theme: break - #if ENABLE_WALLET - case .wallet: - break - #endif case .settings: break case .premiumOffer: @@ -1014,4 +1027,20 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.view.endEditing(true) self.present(controller, in: .window(.root))*/ } + + private func dismissAllTooltips() { + self.antiSpamTooltipController?.dismiss() + + self.controller?.window?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + }) + self.controller?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + return true + }) + } } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift index a12d6a9e49..fbd1414348 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift @@ -45,7 +45,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { case adminsTitle(PresentationTheme, String) case allAdmins(PresentationTheme, String, Bool) - case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Int32, RenderedChannelParticipant, Bool) + case adminPeerItem(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Int32, RenderedChannelParticipant, Bool, Bool) var section: ItemListSectionId { switch self { @@ -68,7 +68,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { return .index(200) case .allAdmins: return .index(201) - case let .adminPeerItem(_, _, _, _, _, participant, _): + case let .adminPeerItem(_, _, _, _, _, participant, _, _): return .peer(participant.peer.id) } } @@ -105,8 +105,8 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { } else { return false } - case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsParticipant, lhsChecked): - if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsParticipant, rhsChecked) = rhs { + case let .adminPeerItem(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsParticipant, lhsIsAntiSpam, lhsChecked): + if case let .adminPeerItem(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsParticipant, rhsIsAntiSpam, rhsChecked) = rhs { if lhsTheme !== rhsTheme { return false } @@ -125,6 +125,9 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { if lhsParticipant != rhsParticipant { return false } + if lhsIsAntiSpam != rhsIsAntiSpam { + return false + } if lhsChecked != rhsChecked { return false } @@ -169,9 +172,9 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { default: return false } - case let .adminPeerItem(_, _, _, _, lhsIndex, _, _): + case let .adminPeerItem(_, _, _, _, lhsIndex, _, _, _): switch rhs { - case let .adminPeerItem(_, _, _, _, rhsIndex, _, _): + case let .adminPeerItem(_, _, _, _, rhsIndex, _, _, _): return lhsIndex < rhsIndex default: return false @@ -198,13 +201,17 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleAllAdmins(value) }) - case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, checked): - let peerText: String - switch participant.participant { + case let .adminPeerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, isAntiSpam, checked): + var peerText: String = "" + if isAntiSpam { + peerText = strings.Group_Management_AntiSpamMagic + } else { + switch participant.participant { case .creator: - peerText = strings.Channel_Management_LabelOwner + peerText = strings.Channel_Management_LabelOwner.lowercased() case .member: - peerText = strings.ChatAdmins_AdminLabel.capitalized + peerText = strings.ChatAdmins_AdminLabel.lowercased() + } } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: { arguments.toggleAdmin(participant.peer.id) @@ -247,7 +254,7 @@ private struct ChatRecentActionsFilterControllerState: Equatable { } } -private func channelRecentActionsFilterControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, peer: Peer, state: ChatRecentActionsFilterControllerState, participants: [RenderedChannelParticipant]?) -> [ChatRecentActionsFilterEntry] { +private func channelRecentActionsFilterControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, peer: Peer, antiSpamBotId: PeerId?, state: ChatRecentActionsFilterControllerState, participants: [RenderedChannelParticipant]?) -> [ChatRecentActionsFilterEntry] { var isGroup = true if let peer = peer as? TelegramChannel, case .broadcast = peer.info { isGroup = false @@ -335,7 +342,7 @@ private func channelRecentActionsFilterControllerEntries(presentationData: Prese } else { adminSelected = true } - entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, participant, adminSelected)) + entries.append(.adminPeerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, participant, participant.peer.id == antiSpamBotId, adminSelected)) index += 1 } } @@ -353,7 +360,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update var dismissImpl: (() -> Void)? let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil) - + let actionsDisposable = DisposableSet() let arguments = ChatRecentActionsFilterControllerArguments(context: context, toggleAllActions: { value in @@ -429,12 +436,25 @@ public func channelRecentActionsFilterController(context: AccountContext, update } actionsDisposable.add(membersDisposable) + let antiSpamBotConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + let antiSpamBotPeerPromise = Promise(nil) + if let antiSpamBotId = antiSpamBotConfiguration.antiSpamBotId { + antiSpamBotPeerPromise.set(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: antiSpamBotId)) + |> map { peer in + if let peer = peer, case let .user(user) = peer { + return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: user) + } else { + return nil + } + }) + } + var previousPeers: [RenderedChannelParticipant]? let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData - let signal = combineLatest(presentationData, statePromise.get(), adminsPromise.get() |> deliverOnMainQueue) + let signal = combineLatest(presentationData, statePromise.get(), adminsPromise.get(), antiSpamBotPeerPromise.get()) |> deliverOnMainQueue - |> map { presentationData, state, admins -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, admins, antiSpamBot -> (ItemListControllerState, (ItemListNodeState, Any)) in let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { dismissImpl?() }) @@ -456,6 +476,9 @@ public func channelRecentActionsFilterController(context: AccountContext, update var sortedAdmins: [RenderedChannelParticipant]? if let admins = admins { sortedAdmins = admins.filter { $0.peer.id == context.account.peerId } + admins.filter({ $0.peer.id != context.account.peerId }) + if let antiSpamBot = antiSpamBot { + sortedAdmins?.insert(antiSpamBot, at: 0) + } } else { sortedAdmins = nil } @@ -464,7 +487,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update previousPeers = sortedAdmins let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatAdmins_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelRecentActionsFilterControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: peer, state: state, participants: sortedAdmins), style: .blocks, animateChanges: previous != nil && admins != nil && previous!.count >= sortedAdmins!.count) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelRecentActionsFilterControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: peer, antiSpamBotId: antiSpamBotConfiguration.antiSpamBotId, state: state, participants: sortedAdmins), style: .blocks, animateChanges: previous != nil && admins != nil && previous!.count >= sortedAdmins!.count) return (controllerState, (listState, arguments)) } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 99c4c93b88..c44b4813c6 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -235,6 +235,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in + }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index a13e7742bf..555b558c5b 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -1215,9 +1215,7 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag return true } else if let peer = peer as? TelegramChannel { if peer.flags.contains(.isForum), let threadData = threadData { - if chatLocation.threadId == 1 { - return false - } else if peer.flags.contains(.isCreator) { + if peer.flags.contains(.isCreator) { return true } else if threadData.isOwnedByMe { return true diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 120395845b..24c10943ee 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -381,7 +381,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } var removedPhotoResourceIds = Set() - func update(peer: Peer?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { + func update(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { if let peer = peer { let previousItem = self.item var item = item @@ -422,7 +422,9 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.iconView = iconView } let content: EmojiStatusComponent.Content - if let iconFileId = threadInfo.icon { + if threadId == 1 { + content = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(theme)) + } else if let iconFileId = threadInfo.icon { content = .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: avatarSize, height: avatarSize), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .forever) } else { content = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: avatarSize, height: avatarSize)) @@ -886,7 +888,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode { let isReady = Promise() - var arguments: (Peer?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)? + var arguments: (Peer?, Int64?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)? var item: PeerInfoAvatarListItem? var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)? @@ -945,14 +947,14 @@ final class PeerInfoAvatarListNode: ASDisplayNode { if let strongSelf = self { strongSelf.item = items.first strongSelf.itemsUpdated?(items) - if let (peer, threadInfo, theme, avatarSize, isExpanded) = strongSelf.arguments { - strongSelf.avatarContainerNode.update(peer: peer, threadInfo: threadInfo, item: strongSelf.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: strongSelf.isSettings) + if let (peer, threadId, threadInfo, theme, avatarSize, isExpanded) = strongSelf.arguments { + strongSelf.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: strongSelf.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: strongSelf.isSettings) } } } self.pinchSourceNode.activate = { [weak self] sourceNode in - guard let strongSelf = self, let (_, _, _, _, isExpanded) = strongSelf.arguments, isExpanded else { + guard let strongSelf = self, let (_, _, _, _, _, isExpanded) = strongSelf.arguments, isExpanded else { return } let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: { @@ -971,11 +973,11 @@ final class PeerInfoAvatarListNode: ASDisplayNode { } } - func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { - self.arguments = (peer, threadInfo, theme, avatarSize, isExpanded) + func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { + self.arguments = (peer, threadId, threadInfo, theme, avatarSize, isExpanded) self.pinchSourceNode.update(size: size, transition: transition) self.pinchSourceNode.frame = CGRect(origin: CGPoint(), size: size) - self.avatarContainerNode.update(peer: peer, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings) + self.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -2953,7 +2955,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { }) } - self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition) + self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, threadId: self.forumTopicThreadId, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition) self.editingContentNode.avatarNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) self.avatarOverlayNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) if additive { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 43c6577b88..41f706cdd1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -2907,7 +2907,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate if case let .replyThread(message) = strongSelf.chatLocation { let threadId = Int64(message.messageId.id) if let threadInfo = strongSelf.data?.threadData?.info { - let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(topic: threadInfo)) + let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(threadId: threadId, threadInfo: threadInfo)) controller.navigationPresentation = .modal let context = strongSelf.context controller.completion = { [weak controller] title, fileId in diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index e08bd3935c..e8efa5558a 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1377,68 +1377,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { return ChatMessageDateHeader(timestamp: timestamp, scheduled: false, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), controllerInteraction: nil, context: context) } - #if ENABLE_WALLET - public func openWallet(context: AccountContext, walletContext: OpenWalletContext, present: @escaping (ViewController) -> Void) { - guard let storedContext = context.tonContext else { - return - } - let _ = (combineLatest(queue: .mainQueue(), - WalletStorageInterfaceImpl(postbox: context.account.postbox).getWalletRecords(), - storedContext.keychain.encryptionPublicKey(), - context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) - ) - |> deliverOnMainQueue).start(next: { wallets, currentPublicKey, preferences in - let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue - let walletConfiguration = WalletConfiguration.with(appConfiguration: appConfiguration) - guard let config = walletConfiguration.config, let blockchainName = walletConfiguration.blockchainName else { - return - } - let tonContext = storedContext.context(config: config, blockchainName: blockchainName, enableProxy: !walletConfiguration.disableProxy) - - if wallets.isEmpty { - if case .send = walletContext { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = textAlertController(context: context, title: presentationData.strings.Conversation_WalletRequiredTitle, text: presentationData.strings.Conversation_WalletRequiredText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Conversation_WalletRequiredNotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Conversation_WalletRequiredSetup, action: { [weak self] in - self?.openWallet(context: context, walletContext: .generic, present: present) - })]) - present(controller) - } else { - if let _ = currentPublicKey { - present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .intro, walletCreatedPreloadState: nil)) - } else { - present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .secureStorageNotAvailable, walletCreatedPreloadState: nil)) - } - } - } else { - let walletInfo = wallets[0].info - let exportCompleted = wallets[0].exportCompleted - if let currentPublicKey = currentPublicKey { - if currentPublicKey == walletInfo.encryptedSecret.publicKey { - let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance) - |> deliverOnMainQueue).start(next: { address in - switch walletContext { - case .generic: - if exportCompleted { - present(WalletInfoScreen(context: WalletContextImpl(context: context, tonContext: tonContext), walletInfo: walletInfo, address: address, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)) - } else { - present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .created(walletInfo, nil), walletCreatedPreloadState: nil)) - } - case let .send(address, amount, comment): - present(walletSendScreen(context: WalletContextImpl(context: context, tonContext: tonContext), randomId: Int64.random(in: Int64.min ... Int64.max), walletInfo: walletInfo, address: address, amount: amount, comment: comment)) - } - - }) - } else { - present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .secureStorageReset(.changed), walletCreatedPreloadState: nil)) - } - } else { - present(WalletSplashScreen(context: WalletContextImpl(context: context, tonContext: tonContext), mode: .secureStorageReset(.notAvailable), walletCreatedPreloadState: nil)) - } - } - }) - } - #endif - public func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let _ = legacyWallpaperPicker(context: context, presentationData: presentationData).start(next: { generator in diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 6bca8ae0b1..c2707160b8 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -195,10 +195,10 @@ public final class ChatTextInputTextUrlAttribute: NSObject { public final class ChatTextInputTextCustomEmojiAttribute: NSObject { public let interactivelySelectedFromPackId: ItemCollectionId? public let fileId: Int64 - public let topicInfo: EngineMessageHistoryThread.Info? + public let topicInfo: (Int64, EngineMessageHistoryThread.Info)? public let file: TelegramMediaFile? - public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, topicInfo: EngineMessageHistoryThread.Info? = nil) { + public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, topicInfo: (Int64, EngineMessageHistoryThread.Info)? = nil) { self.interactivelySelectedFromPackId = interactivelySelectedFromPackId self.fileId = fileId self.file = file diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index ed961f9b17..10868dea54 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -39,7 +39,7 @@ public enum UndoOverlayContent { case mediaSaved(text: String) case paymentSent(currencyValue: String, itemTitle: String) case inviteRequestSent(title: String, text: String) - case image(image: UIImage, text: String) + case image(image: UIImage, title: String?, text: String, undo: Bool) case notificationSoundAdded(title: String, text: String, action: (() -> Void)?) case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?) case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index c41b61b7f0..e7adfb0bf9 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -861,7 +861,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } else { displayUndo = false } - case let .image(image, text): + case let .image(image, title, text, undo): self.avatarNode = nil self.iconNode = ASImageNode() self.iconNode?.clipsToBounds = true @@ -872,9 +872,26 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.iconCheckNode = nil self.animationNode = nil self.animatedStickerNode = nil - self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) - displayUndo = true - self.originalRemainingSeconds = 5 + if let title = title { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + } else { + self.titleNode.attributedText = nil + } + + let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) + let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) + let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in + return ("URL", contents) + }), textAlignment: .natural) + self.textNode.attributedText = attributedText + + displayUndo = undo + self.originalRemainingSeconds = undo ? 5 : 3 + + if text.contains("](") { + isUserInteractionEnabled = true + } case let .peers(context, peers, title, text, customUndoText): self.avatarNode = nil let multiAvatarsNode = AnimatedAvatarSetNode() @@ -1084,8 +1101,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.content = content switch content { - case let .image(image, text): + case let .image(image, title, text, _): self.iconNode?.image = image + if let title = title { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + } else { + self.titleNode.attributedText = nil + } self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) default: break