diff --git a/Telegram/SiriIntents/IntentMessages.swift b/Telegram/SiriIntents/IntentMessages.swift index a85b6e4a8e..e5ae56a823 100644 --- a/Telegram/SiriIntents/IntentMessages.swift +++ b/Telegram/SiriIntents/IntentMessages.swift @@ -36,7 +36,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> { |> mapToSignal { view -> Signal<[INMessage], NoError> in var signals: [Signal<[INMessage], NoError>] = [] for entry in view.0.entries { - if case let .MessageEntry(index, _, readState, isMuted, _, _, _, _, _, _, _, _) = entry { + if case let .MessageEntry(index, _, readState, isMuted, _, _, _, _, _, _, _, _, _) = entry { if index.messageIndex.id.peerId.namespace != Namespaces.Peer.CloudUser { continue } diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index afcfc75bc9..351165e1c7 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8317,3 +8317,5 @@ Sorry for the inconvenience."; "Stickers.EmojiPackInfoText" = "This message contains **%@** emoji."; "PeerInfo.TopicIconInfoText" = "This topic's icon is from **%@**."; + +"AutoremoveSetup.AdditionalGlobalSettingsInfo" = "You can also set your default [self-destruct timer]() for all chats in Settings."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index a1abd7aa0d..e526ec0fc0 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -179,6 +179,8 @@ public enum WallpaperUrlParameter { public enum ResolvedUrlSettingsSection { case theme case devices + case autoremoveMessages + case twoStepAuth } public struct ResolvedBotChoosePeerTypes: OptionSet { diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index bf73485de6..18fb5ade08 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -36,10 +36,35 @@ public struct ContactMultiselectionControllerAdditionalCategories { } public enum ContactMultiselectionControllerMode { + public struct ChatSelection { + public var title: String + public var searchPlaceholder: String + public var selectedChats: Set + public var additionalCategories: ContactMultiselectionControllerAdditionalCategories? + public var chatListFilters: [ChatListFilter]? + public var displayAutoremoveTimeout: Bool + + public init( + title: String, + searchPlaceholder: String, + selectedChats: Set, + additionalCategories: ContactMultiselectionControllerAdditionalCategories?, + chatListFilters: [ChatListFilter]?, + displayAutoremoveTimeout: Bool = false + ) { + self.title = title + self.searchPlaceholder = searchPlaceholder + self.selectedChats = selectedChats + self.additionalCategories = additionalCategories + self.chatListFilters = chatListFilters + self.displayAutoremoveTimeout = displayAutoremoveTimeout + } + } + case groupCreation case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool) case channelCreation - case chatSelection(title: String, selectedChats: Set, additionalCategories: ContactMultiselectionControllerAdditionalCategories?, chatListFilters: [ChatListFilter]?) + case chatSelection(ChatSelection) } public enum ContactListFilter { @@ -54,16 +79,18 @@ public final class ContactMultiselectionControllerParams { public let mode: ContactMultiselectionControllerMode public let options: [ContactListAdditionalOption] public let filters: [ContactListFilter] + public let isPeerEnabled: ((EnginePeer) -> Bool)? public let alwaysEnabled: Bool public let limit: Int32? public let reachedLimit: ((Int32) -> Void)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], isPeerEnabled: ((EnginePeer) -> Bool)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil) { self.context = context self.updatedPresentationData = updatedPresentationData self.mode = mode self.options = options self.filters = filters + self.isPeerEnabled = isPeerEnabled self.alwaysEnabled = alwaysEnabled self.limit = limit self.reachedLimit = reachedLimit diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index c61ed7d527..e838fdcddc 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -601,7 +601,13 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f var pushImpl: ((ViewController) -> Void)? - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_IncludeChatsTitle, selectedChats: Set(filterData.includePeers.peers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection( + title: presentationData.strings.ChatListFolder_IncludeChatsTitle, + searchPlaceholder: presentationData.strings.ChatListFilter_AddChatsTitle, + selectedChats: Set(filterData.includePeers.peers), + additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), + chatListFilters: allFilters + )), options: [], filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in if count >= premiumLimit { let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: min(premiumLimit, count), action: {}) pushImpl?(limitController) @@ -723,7 +729,13 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex selectedCategories.insert(AdditionalExcludeCategoryId.archived.rawValue) } - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_ExcludeChatsTitle, selectedChats: Set(filterData.excludePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true, limit: 100)) + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection( + title: presentationData.strings.ChatListFolder_ExcludeChatsTitle, + searchPlaceholder: presentationData.strings.ChatListFilter_AddChatsTitle, + selectedChats: Set(filterData.excludePeers), + additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), + chatListFilters: allFilters + )), options: [], filters: [], alwaysEnabled: true, limit: 100)) controller.navigationPresentation = .modal let _ = (controller.result |> take(1) diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 6e6e1bb6c6..2bb442fa56 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -157,9 +157,9 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } else if case let .user(user) = primaryPeer { let servicePeer = isServicePeer(primaryPeer._asPeer()) if user.flags.contains(.isSupport) && !servicePeer { - status = .custom(string: strings.Bot_GenericSupportStatus, multiline: false) + status = .custom(string: strings.Bot_GenericSupportStatus, multiline: false, isActive: false, icon: nil) } else if let _ = user.botInfo { - status = .custom(string: strings.Bot_GenericBotStatus, multiline: false) + status = .custom(string: strings.Bot_GenericBotStatus, multiline: false, isActive: false, icon: nil) } else if user.id != context.account.peerId && !servicePeer { let presence = peer.presence ?? TelegramUserPresence(status: .none, lastActivity: 0) status = .presence(EnginePeer.Presence(presence), timeFormat) @@ -167,19 +167,19 @@ private enum ChatListRecentEntry: Comparable, Identifiable { status = .none } } else if case let .legacyGroup(group) = primaryPeer { - status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(group.participantCount)), multiline: false) + status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(group.participantCount)), multiline: false, isActive: false, icon: nil) } else if case let .channel(channel) = primaryPeer { if case .group = channel.info { if let count = peer.subpeerSummary?.count { - status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(count)), multiline: false) + status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(count)), multiline: false, isActive: false, icon: nil) } else { - status = .custom(string: strings.Group_Status, multiline: false) + status = .custom(string: strings.Group_Status, multiline: false, isActive: false, icon: nil) } } else { if let count = peer.subpeerSummary?.count { - status = .custom(string: strings.Conversation_StatusSubscribers(Int32(count)), multiline: false) + status = .custom(string: strings.Conversation_StatusSubscribers(Int32(count)), multiline: false, isActive: false, icon: nil) } else { - status = .custom(string: strings.Channel_Status, multiline: false) + status = .custom(string: strings.Channel_Status, multiline: false, isActive: false, icon: nil) } } } else { @@ -925,7 +925,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { private let presentationDataPromise = Promise() private var searchStateValue = ChatListSearchListPaneNodeState() private let searchStatePromise = ValuePromise() - private let searchContextValue = Atomic(value: nil) + private let searchContextsValue = Atomic<[Int: ChatListSearchMessagesContext]>(value: [:]) var searchCurrentMessages: [EngineMessage]? var currentEntries: [ChatListSearchEntry]? @@ -1081,11 +1081,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self.addSubnode(self.shimmerNode) self.addSubnode(self.mediaAccessoryPanelContainer) - let searchContext = Promise(nil) - let searchContextValue = self.searchContextValue - let updateSearchContext: ((ChatListSearchMessagesContext?) -> (ChatListSearchMessagesContext?, Bool)) -> Void = { f in + let searchContexts = Promise<[Int: ChatListSearchMessagesContext]>([:]) + let searchContextsValue = self.searchContextsValue + let updateSearchContexts: (([Int: ChatListSearchMessagesContext]) -> ([Int: ChatListSearchMessagesContext], Bool)) -> Void = { f in var shouldUpdate = false - let updated = searchContextValue.modify { current in + let updated = searchContextsValue.modify { current in let (u, s) = f(current) shouldUpdate = s if s { @@ -1095,7 +1095,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } if shouldUpdate { - searchContext.set(.single(updated)) + searchContexts.set(.single(updated)) } } @@ -1419,32 +1419,32 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } else { foundRemotePeers = .single(([], [], false)) } - let searchLocation: SearchMessagesLocation + let searchLocations: [SearchMessagesLocation] if let options = options { if case let .forum(peerId) = location { - searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1) + searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)] } else if let (peerId, _, _) = options.peer { - searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1) + searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1)] } else { if case let .chatList(groupId) = location, case .archive = groupId { - searchLocation = .group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1) + searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)] } else { - searchLocation = .general(tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1) + searchLocations = [.general(tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)] } } } else { if case let .forum(peerId) = location { - searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: nil, maxDate: nil) + searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: nil, maxDate: nil), .general(tags: tagMask, minDate: nil, maxDate: nil)] } else if case let .chatList(groupId) = location, case .archive = groupId { - searchLocation = .group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil) + searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)] } else { - searchLocation = .general(tags: tagMask, minDate: nil, maxDate: nil) + searchLocations = [.general(tags: tagMask, minDate: nil, maxDate: nil)] } } let finalQuery = query ?? "" - updateSearchContext { _ in - return (nil, true) + updateSearchContexts { _ in + return ([:], true) } let foundRemoteMessages: Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool), NoError> if peersFilter.contains(.doNotSearchMessages) { @@ -1454,23 +1454,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { addAppLogEvent(postbox: context.account.postbox, type: "search_global_query") } - let searchSignal = context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50) - |> map { result, updatedState -> ChatListSearchMessagesResult in + let searchSignals: [Signal<(SearchMessagesResult, SearchMessagesState), NoError>] = searchLocations.map { searchLocation in + return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50) + } + + let searchSignal = combineLatest(searchSignals) + |> map { results -> ChatListSearchMessagesResult in + let (result, updatedState) = results[0] return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.map({ EngineMessage($0) }).sorted(by: { $0.index > $1.index }), readStates: result.readStates.mapValues { EnginePeerReadCounters(state: $0, isMuted: false) }, threadInfo: result.threadInfo, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState) } - let loadMore = searchContext.get() - |> mapToSignal { searchContext -> Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool), NoError> in - if let searchContext = searchContext, searchContext.result.hasMore { + let loadMore = searchContexts.get() + |> mapToSignal { searchContexts -> Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool), NoError> in + if let searchContext = searchContexts[0], searchContext.result.hasMore { if let _ = searchContext.loadMoreIndex { - return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: searchContext.result.state, limit: 80) + return context.engine.messages.searchMessages(location: searchLocations[0], query: finalQuery, state: searchContext.result.state, limit: 80) |> map { result, updatedState -> ChatListSearchMessagesResult in return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.map({ EngineMessage($0) }).sorted(by: { $0.index > $1.index }), readStates: result.readStates.mapValues { EnginePeerReadCounters(state: $0, isMuted: false) }, threadInfo: result.threadInfo, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState) } |> mapToSignal { foundMessages -> Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool), NoError> in - updateSearchContext { previous in + updateSearchContexts { previous in let updated = ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil) - return (updated, true) + return ([0: updated], true) } return .complete() } @@ -1486,8 +1491,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { |> then( searchSignal |> map { foundMessages -> (([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool) in - updateSearchContext { _ in - return (ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil), true) + updateSearchContexts { _ in + return ([0: ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil)], true) } return ((foundMessages.messages, foundMessages.readStates, foundMessages.threadInfo, foundMessages.totalCount), false) } @@ -1773,7 +1778,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } - let foundMessages = searchContext.get() |> map { searchContext -> ([EngineMessage], Int32, Bool) in + let foundMessages = searchContexts.get() |> map { searchContexts -> ([EngineMessage], Int32, Bool) in + let searchContext = searchContexts[0] if let result = searchContext?.result { return (result.messages, result.totalCount, result.hasMore) } else { @@ -1782,17 +1788,17 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } let loadMore = { - updateSearchContext { previous in - guard let previous = previous else { - return (nil, false) + updateSearchContexts { previousMap in + guard let previous = previousMap[0] else { + return ([:], false) } if previous.loadMoreIndex != nil { - return (previous, false) + return ([0: previous], false) } guard let last = previous.result.messages.last else { - return (previous, false) + return ([0: previous], false) } - return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true) + return ([0: ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index)], true) } } @@ -3302,7 +3308,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { if let itemNode = itemNodes[sampleIndex] as? ChatListItemNode { if !isInlineMode { if !itemNode.avatarNode.isHidden { - context.fillEllipse(in: itemNode.avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) + context.fillEllipse(in: itemNode.avatarNode.view.convert(itemNode.avatarNode.bounds, to: itemNode.view).offsetBy(dx: 0.0, dy: currentY)) } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 6e44038892..b683a8e789 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2409,7 +2409,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var mainContentAlpha: CGFloat = 1.0 if case .chatList = item.chatListLocation { - mainContentFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + mainContentFrame = CGRect(origin: CGPoint(x: leftInset - 2.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) mainContentBoundsOffset = mainContentFrame.origin.x if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 168863bc86..668187334c 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -20,7 +20,7 @@ import Postbox public enum ChatListNodeMode { case chatList - case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?) + case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool) } struct ChatListNodeListViewTransition { @@ -292,7 +292,7 @@ public struct ChatListNodeState: Equatable { } } -private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { +private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { return entries.map { entry -> ListViewInsertItem in switch entry.entry { case .HeaderEntry: @@ -315,7 +315,28 @@ 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, revealed): + case let .PeerEntry(peerEntry): + let index = peerEntry.index + let presentationData = peerEntry.presentationData + let combinedReadState = peerEntry.readState + let isRemovedFromTotalUnreadCount = peerEntry.isRemovedFromTotalUnreadCount + let draftState = peerEntry.draftState + let peer = peerEntry.peer + let threadInfo = peerEntry.threadInfo + let presence = peerEntry.presence + let hasUnseenMentions = peerEntry.hasUnseenMentions + let hasUnseenReactions = peerEntry.hasUnseenReactions + let editing = peerEntry.editing + let hasActiveRevealControls = peerEntry.hasActiveRevealControls + let selected = peerEntry.selected + let inputActivities = peerEntry.inputActivities + let promoInfo = peerEntry.promoInfo + let hasFailedMessages = peerEntry.hasFailedMessages + let isContact = peerEntry.isContact + let forumTopicData = peerEntry.forumTopicData + let topForumTopicItems = peerEntry.topForumTopicItems + let revealed = peerEntry.revealed + switch mode { case .chatList: return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -325,7 +346,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL filterData: filterData, index: index, content: .peer( - messages: messages, + messages: peerEntry.messages, peer: peer, threadInfo: threadInfo, combinedReadState: combinedReadState, @@ -350,84 +371,90 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL hiddenOffset: threadInfo?.isHidden == true && !revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) - case let .peers(filter, isSelecting, _, filters): + case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout): let itemPeer = peer.chatMainPeer var chatPeer: EnginePeer? if let peer = peer.peers[peer.peerId] { chatPeer = peer } var enabled = true - if filter.contains(.onlyWriteable) { - if let peer = peer.peers[peer.peerId] { - if !canSendMessagesToPeer(peer._asPeer()) { + if let isPeerEnabled { + if let itemPeer { + enabled = isPeerEnabled(itemPeer) + } + } else { + if filter.contains(.onlyWriteable) { + if let peer = peer.peers[peer.peerId] { + if !canSendMessagesToPeer(peer._asPeer()) { + enabled = false + } + } else { enabled = false } - } else { - enabled = false + + if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer { + if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) { + } else { + enabled = false + } + } } - - if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer { - if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) { + if filter.contains(.onlyPrivateChats) { + if let peer = peer.peers[peer.peerId] { + switch peer { + case .user, .secretChat: + break + default: + enabled = false + } } else { enabled = false } } - } - if filter.contains(.onlyPrivateChats) { - if let peer = peer.peers[peer.peerId] { - switch peer { - case .user, .secretChat: - break - default: - enabled = false - } - } else { - enabled = false - } - } - if filter.contains(.onlyGroups) { - if let peer = peer.peers[peer.peerId] { - if case .legacyGroup = peer { - } else if case let .channel(peer) = peer, case .group = peer.info { + if filter.contains(.onlyGroups) { + if let peer = peer.peers[peer.peerId] { + if case .legacyGroup = peer { + } else if case let .channel(peer) = peer, case .group = peer.info { + } else { + enabled = false + } } else { enabled = false } - } else { - enabled = false } - } - if filter.contains(.onlyManageable) { - if let peer = peer.peers[peer.peerId] { - var canManage = false - if case let .legacyGroup(peer) = peer { - switch peer.role { + if filter.contains(.onlyManageable) { + if let peer = peer.peers[peer.peerId] { + var canManage = false + if case let .legacyGroup(peer) = peer { + switch peer.role { case .creator, .admin: canManage = true default: break + } + } + + if canManage { + } else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) { + } else { + enabled = false } - } - - if canManage { - } else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) { } else { enabled = false } - } else { - enabled = false } - } - if filter.contains(.excludeChannels) { - if let peer = peer.peers[peer.peerId] { - if case let .channel(peer) = peer, case .broadcast = peer.info { - enabled = false + if filter.contains(.excludeChannels) { + if let peer = peer.peers[peer.peerId] { + if case let .channel(peer) = peer, case .broadcast = peer.info { + enabled = false + } } } } var header: ChatListSearchItemHeader? switch mode { - case let .peers(_, _, additionalCategories, _): + case let .peers(_, _, additionalCategories, _, _): if !additionalCategories.isEmpty { let headerType: ChatListSearchItemHeaderType if case .action = additionalCategories[0].appearance { @@ -444,8 +471,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL var status: ContactsPeerItemStatus = .none if isSelecting, let itemPeer = itemPeer { - if let (string, multiline) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters) { - status = .custom(string: string, multiline: multiline) + if let (string, multiline, isActive, icon) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters, displayAutoremoveTimeout: displayAutoremoveTimeout, autoremoveTimeout: peerEntry.autoremoveTimeout) { + status = .custom(string: string, multiline: multiline, isActive: isActive, icon: icon) } else { status = .none } @@ -541,10 +568,31 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL } } -private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { +private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, 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, revealed): + case let .PeerEntry(peerEntry): + let index = peerEntry.index + let presentationData = peerEntry.presentationData + let combinedReadState = peerEntry.readState + let isRemovedFromTotalUnreadCount = peerEntry.isRemovedFromTotalUnreadCount + let draftState = peerEntry.draftState + let peer = peerEntry.peer + let threadInfo = peerEntry.threadInfo + let presence = peerEntry.presence + let hasUnseenMentions = peerEntry.hasUnseenMentions + let hasUnseenReactions = peerEntry.hasUnseenReactions + let editing = peerEntry.editing + let hasActiveRevealControls = peerEntry.hasActiveRevealControls + let selected = peerEntry.selected + let inputActivities = peerEntry.inputActivities + let promoInfo = peerEntry.promoInfo + let hasFailedMessages = peerEntry.hasFailedMessages + let isContact = peerEntry.isContact + let forumTopicData = peerEntry.forumTopicData + let topForumTopicItems = peerEntry.topForumTopicItems + let revealed = peerEntry.revealed + switch mode { case .chatList: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -554,7 +602,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL filterData: filterData, index: index, content: .peer( - messages: messages, + messages: peerEntry.messages, peer: peer, threadInfo: threadInfo, combinedReadState: combinedReadState, @@ -579,37 +627,44 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL hiddenOffset: threadInfo?.isHidden == true && !revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) - case let .peers(filter, isSelecting, _, filters): + case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout): let itemPeer = peer.chatMainPeer var chatPeer: EnginePeer? if let peer = peer.peers[peer.peerId] { chatPeer = peer } var enabled = true - if filter.contains(.onlyWriteable) { - if let peer = peer.peers[peer.peerId] { - if !canSendMessagesToPeer(peer._asPeer()) { - enabled = false - } - } else { - enabled = false + if let isPeerEnabled { + if let itemPeer { + enabled = isPeerEnabled(itemPeer) } - - if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer { - if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) { + } else { + if filter.contains(.onlyWriteable) { + if let peer = peer.peers[peer.peerId] { + if !canSendMessagesToPeer(peer._asPeer()) { + enabled = false + } } else { enabled = false } + + if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer { + if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) { + } else { + enabled = false + } + } + } + if filter.contains(.excludeChannels) { + if case let .channel(peer) = peer.chatMainPeer, case .broadcast = peer.info { + enabled = false + } } } - if filter.contains(.excludeChannels) { - if case let .channel(peer) = peer.chatMainPeer, case .broadcast = peer.info { - enabled = false - } - } + var header: ChatListSearchItemHeader? switch mode { - case let .peers(_, _, additionalCategories, _): + case let .peers(_, _, additionalCategories, _, _): if !additionalCategories.isEmpty { let headerType: ChatListSearchItemHeaderType if case .action = additionalCategories[0].appearance { @@ -626,8 +681,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL var status: ContactsPeerItemStatus = .none if isSelecting, let itemPeer = itemPeer { - if let (string, multiline) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters) { - status = .custom(string: string, multiline: multiline) + if let (string, multiline, isActive, icon) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters, displayAutoremoveTimeout: displayAutoremoveTimeout, autoremoveTimeout: peerEntry.autoremoveTimeout) { + status = .custom(string: string, multiline: multiline, isActive: isActive, icon: icon) } else { status = .none } @@ -743,8 +798,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL } } -private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { - return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade) +private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { + return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade) } private final class ChatListOpaqueTransactionState { @@ -907,7 +962,7 @@ public final class ChatListNode: ListView { let hideArhiveIntro = ValuePromise(false, ignoreRepeated: true) - public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) { + public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) { self.context = context self.location = location self.chatListFilter = chatListFilter @@ -918,7 +973,7 @@ public final class ChatListNode: ListView { self.animationRenderer = animationRenderer var isSelecting = false - if case .peers(_, true, _, _) = mode { + if case .peers(_, true, _, _, _) = mode { isSelecting = true } @@ -950,6 +1005,12 @@ public final class ChatListNode: ListView { guard let strongSelf = self else { return } + if case .peers = strongSelf.mode { + if let strongSelf = self, let peerSelected = strongSelf.peerSelected { + peerSelected(peer, nil, true, true, nil) + } + return + } var didBeginSelecting = false var count = 0 strongSelf.updateState { [weak self] state in @@ -1213,7 +1274,7 @@ public final class ChatListNode: ListView { let currentRemovingItemId = self.currentRemovingItemId let savedMessagesPeer: Signal - if case let .peers(filter, _, _, _) = mode, filter.contains(.onlyWriteable), case .chatList = location { + if case let .peers(filter, _, _, _, _) = mode, filter.contains(.onlyWriteable), case .chatList = location { savedMessagesPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> map(Optional.init) |> map { peer in @@ -1269,11 +1330,13 @@ 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(peerEntry): + let peer = peerEntry.peer + switch mode { case .chatList: return true - case let .peers(filter, _, _, _): + case let .peers(filter, _, _, _, _): guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false } guard !filter.contains(.excludeSavedMessages) || !peer.peerId.isReplies else { return false } guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false } @@ -1444,7 +1507,10 @@ public final class ChatListNode: ListView { var didIncludeHiddenThread = false if let previous = previousView { for entry in previous.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(peerEntry) = entry { + let index = peerEntry.index + let threadInfo = peerEntry.threadInfo + if let threadInfo, threadInfo.isHidden { didIncludeHiddenThread = true } @@ -1474,7 +1540,10 @@ public final class ChatListNode: ListView { var doesIncludeHiddenThread = false for entry in processedView.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(peerEntry) = entry { + let index = peerEntry.index + let threadInfo = peerEntry.threadInfo + if let threadInfo, threadInfo.isHidden { doesIncludeHiddenThread = true } @@ -1547,7 +1616,7 @@ public final class ChatListNode: ListView { } return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode) - |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, transition: $0) }) + |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, isPeerEnabled: isPeerEnabled, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue) } @@ -1585,7 +1654,9 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(_, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(peerEntry): + let threadInfo = peerEntry.threadInfo + if let threadInfo, threadInfo.isHidden { isHiddenItemVisible = true } @@ -1792,7 +1863,10 @@ public final class ChatListNode: ListView { var referenceId: EngineChatList.PinnedItem.Id? var beforeAll = false switch toEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): + case let .PeerEntry(peerEntry): + let index = peerEntry.index + let promoInfo = peerEntry.promoInfo + if promoInfo != nil { beforeAll = true } else { @@ -1819,8 +1893,8 @@ public final class ChatListNode: ListView { var itemId: EngineChatList.PinnedItem.Id? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - if case let .chatList(index) = index { + case let .PeerEntry(peerEntry): + if case let .chatList(index) = peerEntry.index { itemId = .peer(index.messageIndex.id.peerId) } default: @@ -1865,11 +1939,11 @@ public final class ChatListNode: ListView { var referenceId: Int64? var beforeAll = false switch toEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): - if promoInfo != nil { + case let .PeerEntry(peerEntry): + if peerEntry.promoInfo != nil { beforeAll = true } else { - if case let .forum(_, _, threadId, _, _) = index { + if case let .forum(_, _, threadId, _, _) = peerEntry.index { referenceId = threadId } } @@ -1885,8 +1959,8 @@ public final class ChatListNode: ListView { var itemId: Int64? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - if case let .forum(_, _, threadId, _, _) = index { + case let .PeerEntry(peerEntry): + if case let .forum(_, _, threadId, _, _) = peerEntry.index { itemId = threadId } default: @@ -2154,15 +2228,15 @@ public final class ChatListNode: ListView { if !transition.chatListView.originalList.hasLater { for entry in filteredEntries.reversed() { switch entry { - case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): - if promoInfo == nil { + case let .PeerEntry(peerEntry): + if peerEntry.promoInfo == nil { var hasUnread = false - if let combinedReadState = combinedReadState { + if let combinedReadState = peerEntry.readState { hasUnread = combinedReadState.count > 0 } - switch index { + switch peerEntry.index { case let .chatList(index): - preloadItems.append(ChatHistoryPreloadItem(index: index, threadId: nil, isMuted: isMuted, hasUnread: hasUnread)) + preloadItems.append(ChatHistoryPreloadItem(index: index, threadId: nil, isMuted: peerEntry.isRemovedFromTotalUnreadCount, hasUnread: hasUnread)) case .forum: break } @@ -2271,7 +2345,7 @@ public final class ChatListNode: ListView { case .GroupReferenceEntry, .HoleEntry, .PeerEntry: containsChats = true if case .forum = strongSelf.location { - if case let .PeerEntry(_, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry, let threadInfo { + if case let .PeerEntry(peerEntry) = entry, let threadInfo = peerEntry.threadInfo { if threadInfo.id == 1 { hasGeneral = true } @@ -2527,9 +2601,9 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peer.peerId) { - current = (index, peer.peer!, entryCount - i - 1) + case let .PeerEntry(peerEntry): + if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peerEntry.peer.peerId) { + current = (peerEntry.index, peerEntry.peer.peer!, entryCount - i - 1) break outer } default: @@ -2574,11 +2648,11 @@ 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] { - next = (index, peer.peer!) + if current.2 > 0, case let .PeerEntry(peerEntry) = chatListView.filteredEntries[current.2 - 1] { + next = (peerEntry.index, peerEntry.peer.peer!) } - if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { - previous = (index, peer.peer!) + if current.2 <= entryCount - 2, case let .PeerEntry(peerEntry) = chatListView.filteredEntries[current.2 + 1] { + previous = (peerEntry.index, peerEntry.peer.peer!) } if case .previous = option { target = previous @@ -2586,8 +2660,8 @@ public final class ChatListNode: ListView { target = next } } else if entryCount > 0 { - if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { - target = (index, peer.peer!) + if case let .PeerEntry(peerEntry) = chatListView.filteredEntries[entryCount - 1] { + target = (peerEntry.index, peerEntry.peer.peer!) } } if let target = target { @@ -2664,8 +2738,8 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - return index + case let .PeerEntry(peerEntry): + return peerEntry.index default: break } @@ -2889,11 +2963,21 @@ public final class ChatListNode: ListView { } } -private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: PresentationStrings, peer: EnginePeer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> (String, Bool)? { +private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: PresentationStrings, peer: EnginePeer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, autoremoveTimeout: Int32?) -> (String, Bool, Bool, ContactsPeerItemStatus.Icon?)? { if accountPeerId == peer.id { return nil } + if displayAutoremoveTimeout { + if let autoremoveTimeout = autoremoveTimeout { + //TODO:localize + return ("auto-delete after \(timeIntervalString(strings: strings, value: autoremoveTimeout))", false, true, .autoremove) + } else { + //TODO:localize + return ("auto-deletion disabled", false, false, .autoremove) + } + } + if let chatListFilters = chatListFilters { var result = "" for case let .filter(_, title, _, data) in chatListFilters { @@ -2909,7 +2993,7 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres if result.isEmpty { return nil } else { - return (result, true) + return (result, true, false, nil) } } @@ -2917,28 +3001,28 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres return nil } else if case let .user(user) = peer { if user.botInfo != nil || user.flags.contains(.isSupport) { - return (strings.ChatList_PeerTypeBot, false) + return (strings.ChatList_PeerTypeBot, false, false, nil) } else if isContact { - return (strings.ChatList_PeerTypeContact, false) + return (strings.ChatList_PeerTypeContact, false, false, nil) } else { - return (strings.ChatList_PeerTypeNonContact, false) + return (strings.ChatList_PeerTypeNonContact, false, false, nil) } } else if case .secretChat = peer { if isContact { - return (strings.ChatList_PeerTypeContact, false) + return (strings.ChatList_PeerTypeContact, false, false, nil) } else { - return (strings.ChatList_PeerTypeNonContact, false) + return (strings.ChatList_PeerTypeNonContact, false, false, nil) } } else if case .legacyGroup = peer { - return (strings.ChatList_PeerTypeGroup, false) + return (strings.ChatList_PeerTypeGroup, false, false, nil) } else if case let .channel(channel) = peer { if case .group = channel.info { - return (strings.ChatList_PeerTypeGroup, false) + return (strings.ChatList_PeerTypeGroup, false, false, nil) } else { - return (strings.ChatList_PeerTypeChannel, false) + return (strings.ChatList_PeerTypeChannel, false, false, nil) } } - return (strings.ChatList_PeerTypeNonContact, false) + return (strings.ChatList_PeerTypeNonContact, false, false, nil) } public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 0356bf510c..613b099580 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -46,30 +46,191 @@ public enum ChatListNodeEntryPromoInfo: Equatable { } enum ChatListNodeEntry: Comparable, Identifiable { + struct PeerEntryData: Equatable { + var index: EngineChatList.Item.Index + var presentationData: ChatListPresentationData + var messages: [EngineMessage] + var readState: EnginePeerReadCounters? + var isRemovedFromTotalUnreadCount: Bool + var draftState: ChatListItemContent.DraftState? + var peer: EngineRenderedPeer + var threadInfo: ChatListItemContent.ThreadInfo? + var presence: EnginePeer.Presence? + var hasUnseenMentions: Bool + var hasUnseenReactions: Bool + var editing: Bool + var hasActiveRevealControls: Bool + var selected: Bool + var inputActivities: [(EnginePeer, PeerInputActivity)]? + var promoInfo: ChatListNodeEntryPromoInfo? + var hasFailedMessages: Bool + var isContact: Bool + var autoremoveTimeout: Int32? + var forumTopicData: EngineChatList.ForumTopicData? + var topForumTopicItems: [EngineChatList.ForumTopicData] + var revealed: Bool + + init( + index: EngineChatList.Item.Index, + presentationData: ChatListPresentationData, + messages: [EngineMessage], + readState: EnginePeerReadCounters?, + isRemovedFromTotalUnreadCount: Bool, + draftState: ChatListItemContent.DraftState?, + peer: EngineRenderedPeer, + threadInfo: ChatListItemContent.ThreadInfo?, + presence: EnginePeer.Presence?, + hasUnseenMentions: Bool, + hasUnseenReactions: Bool, + editing: Bool, + hasActiveRevealControls: Bool, + selected: Bool, + inputActivities: [(EnginePeer, PeerInputActivity)]?, + promoInfo: ChatListNodeEntryPromoInfo?, + hasFailedMessages: Bool, + isContact: Bool, + autoremoveTimeout: Int32?, + forumTopicData: EngineChatList.ForumTopicData?, + topForumTopicItems: [EngineChatList.ForumTopicData], + revealed: Bool + ) { + self.index = index + self.presentationData = presentationData + self.messages = messages + self.readState = readState + self.isRemovedFromTotalUnreadCount = isRemovedFromTotalUnreadCount + self.draftState = draftState + self.peer = peer + self.threadInfo = threadInfo + self.presence = presence + self.hasUnseenMentions = hasUnseenMentions + self.hasUnseenReactions = hasUnseenReactions + self.editing = editing + self.hasActiveRevealControls = hasActiveRevealControls + self.selected = selected + self.inputActivities = inputActivities + self.promoInfo = promoInfo + self.hasFailedMessages = hasFailedMessages + self.isContact = isContact + self.autoremoveTimeout = autoremoveTimeout + self.forumTopicData = forumTopicData + self.topForumTopicItems = topForumTopicItems + self.revealed = revealed + } + + static func ==(lhs: PeerEntryData, rhs: PeerEntryData) -> Bool { + if lhs.index != rhs.index { + return false + } + if lhs.presentationData !== rhs.presentationData { + return false + } + if lhs.readState != rhs.readState { + return false + } + if lhs.messages.count != rhs.messages.count { + return false + } + for i in 0 ..< lhs.messages.count { + if lhs.messages[i].stableVersion != rhs.messages[i].stableVersion { + return false + } + if lhs.messages[i].id != rhs.messages[i].id { + return false + } + if lhs.messages[i].associatedMessages.count != rhs.messages[i].associatedMessages.count { + return false + } + for (id, message) in lhs.messages[i].associatedMessages { + if let otherMessage = rhs.messages[i].associatedMessages[id] { + if message.stableVersion != otherMessage.stableVersion { + return false + } + } else { + return false + } + } + } + if lhs.isRemovedFromTotalUnreadCount != rhs.isRemovedFromTotalUnreadCount { + return false + } + if let lhsPeerPresence = lhs.presence, let rhsPeerPresence = rhs.presence { + if lhsPeerPresence != rhsPeerPresence { + return false + } + } else if (lhs.presence != nil) != (rhs.presence != nil) { + return false + } + if let lhsEmbeddedState = lhs.draftState, let rhsEmbeddedState = rhs.draftState { + if lhsEmbeddedState != rhsEmbeddedState { + return false + } + } else if (lhs.draftState != nil) != (rhs.draftState != nil) { + return false + } + if lhs.editing != rhs.editing { + return false + } + if lhs.hasActiveRevealControls != rhs.hasActiveRevealControls { + return false + } + if lhs.selected != rhs.selected { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.threadInfo != rhs.threadInfo { + return false + } + if lhs.hasUnseenMentions != rhs.hasUnseenMentions { + return false + } + if lhs.hasUnseenReactions != rhs.hasUnseenReactions { + return false + } + if let lhsInputActivities = lhs.inputActivities, let rhsInputActivities = rhs.inputActivities { + if lhsInputActivities.count != rhsInputActivities.count { + return false + } + for i in 0 ..< lhsInputActivities.count { + if lhsInputActivities[i].0 != rhsInputActivities[i].0 { + return false + } + if lhsInputActivities[i].1 != rhsInputActivities[i].1 { + return false + } + } + } else if (lhs.inputActivities != nil) != (rhs.inputActivities != nil) { + return false + } + if lhs.promoInfo != rhs.promoInfo { + return false + } + if lhs.hasFailedMessages != rhs.hasFailedMessages { + return false + } + if lhs.isContact != rhs.isContact { + return false + } + if lhs.autoremoveTimeout != rhs.autoremoveTimeout { + return false + } + if lhs.forumTopicData != rhs.forumTopicData { + return false + } + if lhs.topForumTopicItems != rhs.topForumTopicItems { + return false + } + if lhs.revealed != rhs.revealed { + return false + } + return true + } + } + case HeaderEntry - case PeerEntry( - index: EngineChatList.Item.Index, - presentationData: ChatListPresentationData, - messages: [EngineMessage], - readState: EnginePeerReadCounters?, - isRemovedFromTotalUnreadCount: Bool, - draftState: ChatListItemContent.DraftState?, - peer: EngineRenderedPeer, - threadInfo: ChatListItemContent.ThreadInfo?, - presence: EnginePeer.Presence?, - hasUnseenMentions: Bool, - hasUnseenReactions: Bool, - editing: Bool, - hasActiveRevealControls: Bool, - selected: Bool, - inputActivities: [(EnginePeer, PeerInputActivity)]?, - promoInfo: ChatListNodeEntryPromoInfo?, - hasFailedMessages: Bool, - isContact: Bool, - forumTopicData: EngineChatList.ForumTopicData?, - topForumTopicItems: [EngineChatList.ForumTopicData], - revealed: Bool - ) + case PeerEntry(PeerEntryData) case HoleEntry(EngineMessage.Index, theme: PresentationTheme) case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool) case ArchiveIntro(presentationData: ChatListPresentationData) @@ -79,8 +240,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .index(.chatList(.absoluteUpperBound)) - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - return .index(index) + case let .PeerEntry(peerEntry): + return .index(peerEntry.index) case let .HoleEntry(holeIndex, _): return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex))) case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _): @@ -96,8 +257,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .Header - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): - switch index { + case let .PeerEntry(peerEntry): + switch peerEntry.index { case let .chatList(index): return .PeerId(index.messageIndex.id.peerId.toInt64()) case let .forum(_, _, threadId, _, _): @@ -126,115 +287,11 @@ 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, 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, rhsRevealed): - if lhsIndex != rhsIndex { - return false - } - if lhsPresentationData !== rhsPresentationData { - return false - } - if lhsUnreadCount != rhsUnreadCount { - return false - } - if lhsMessages.count != rhsMessages.count { - return false - } - for i in 0 ..< lhsMessages.count { - if lhsMessages[i].stableVersion != rhsMessages[i].stableVersion { - return false - } - if lhsMessages[i].id != rhsMessages[i].id { - return false - } - if lhsMessages[i].associatedMessages.count != rhsMessages[i].associatedMessages.count { - return false - } - for (id, message) in lhsMessages[i].associatedMessages { - if let otherMessage = rhsMessages[i].associatedMessages[id] { - if message.stableVersion != otherMessage.stableVersion { - return false - } - } else { - return false - } - } - } - if lhsIsRemovedFromTotalUnreadCount != rhsIsRemovedFromTotalUnreadCount { - return false - } - if let lhsPeerPresence = lhsPresence, let rhsPeerPresence = rhsPresence { - if lhsPeerPresence != rhsPeerPresence { - return false - } - } else if (lhsPresence != nil) != (rhsPresence != nil) { - return false - } - if let lhsEmbeddedState = lhsEmbeddedState, let rhsEmbeddedState = rhsEmbeddedState { - if lhsEmbeddedState != rhsEmbeddedState { - return false - } - } else if (lhsEmbeddedState != nil) != (rhsEmbeddedState != nil) { - return false - } - if lhsEditing != rhsEditing { - return false - } - if lhsHasRevealControls != rhsHasRevealControls { - return false - } - if lhsSelected != rhsSelected { - return false - } - if lhsPeer != rhsPeer { - return false - } - if lhsThreadInfo != rhsThreadInfo { - return false - } - if lhsHasUnseenMentions != rhsHasUnseenMentions { - return false - } - if lhsHasUnseenReactions != rhsHasUnseenReactions { - return false - } - if let lhsInputActivities = lhsInputActivities, let rhsInputActivities = rhsInputActivities { - if lhsInputActivities.count != rhsInputActivities.count { - return false - } - for i in 0 ..< lhsInputActivities.count { - if lhsInputActivities[i].0 != rhsInputActivities[i].0 { - return false - } - if lhsInputActivities[i].1 != rhsInputActivities[i].1 { - return false - } - } - } else if (lhsInputActivities != nil) != (rhsInputActivities != nil) { - return false - } - if lhsAd != rhsAd { - return false - } - if lhsHasFailedMessages != rhsHasFailedMessages { - return false - } - if lhsIsContact != rhsIsContact { - return false - } - if lhsForumThreadTitle != rhsForumThreadTitle { - return false - } - if lhsTopForumTopicItems != rhsTopForumTopicItems { - return false - } - if lhsRevealed != rhsRevealed { - return false - } - return true - default: - return false + case let .PeerEntry(peerEntry): + if case .PeerEntry(peerEntry) = rhs { + return true + } else { + return false } case let .HoleEntry(lhsHole, lhsTheme): switch rhs { @@ -401,7 +458,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden) } - result.append(.PeerEntry( + result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData( index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, @@ -420,10 +477,11 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, + autoremoveTimeout: entry.autoremoveTimeout, forumTopicData: entry.forumTopicData, topForumTopicItems: entry.topForumTopicItems, revealed: threadId == 1 && state.hiddenItemShouldBeTemporaryRevealed - )) + ))) } if !view.hasLater { var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) @@ -438,7 +496,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState } let messageIndex = EngineMessage.Index(id: EngineMessage.Id(peerId: peer.0.id, namespace: 0, id: 0), timestamp: 1) - result.append(.PeerEntry( + result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData( index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: foundPinningIndex, messageIndex: messageIndex)), presentationData: state.presentationData, messages: [], @@ -457,17 +515,18 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo: nil, hasFailedMessages: false, isContact: false, + autoremoveTimeout: nil, forumTopicData: nil, topForumTopicItems: [], revealed: false - )) + ))) if foundPinningIndex != 0 { foundPinningIndex -= 1 } } } - result.append(.PeerEntry( + result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData( index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], @@ -486,10 +545,11 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo: nil, hasFailedMessages: false, isContact: false, + autoremoveTimeout: nil, forumTopicData: nil, topForumTopicItems: [], revealed: false - )) + ))) } else { if !filteredAdditionalItemEntries.isEmpty { for item in filteredAdditionalItemEntries.reversed() { @@ -517,7 +577,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState break } - result.append(.PeerEntry( + result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData( index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: index.messageIndex)), presentationData: state.presentationData, messages: item.item.messages, @@ -536,10 +596,11 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo: promoInfo, hasFailedMessages: item.item.hasFailed, isContact: item.item.isContact, + autoremoveTimeout: item.item.autoremoveTimeout, forumTopicData: item.item.forumTopicData, topForumTopicItems: item.item.topForumTopicItems, revealed: threadId == 1 && state.hiddenItemShouldBeTemporaryRevealed - )) + ))) if pinningIndex != 0 { pinningIndex -= 1 } @@ -573,7 +634,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState result.append(.HeaderEntry) } - if !view.hasLater, case let .peers(_, _, additionalCategories, _) = mode { + if !view.hasLater, case let .peers(_, _, additionalCategories, _, _) = mode { var index = 0 for category in additionalCategories.reversed(){ result.append(.AdditionalCategory(index: index, id: category.id, title: category.title, image: category.icon, appearance: category.appearance, selected: state.selectedAdditionalCategoryIds.contains(category.id), presentationData: state.presentationData)) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 8bad00a789..6737b466dd 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -287,7 +287,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat forumTopicData: nil, topForumTopicItems: [], hasFailed: false, - isContact: false + isContact: false, + autoremoveTimeout: nil )) } diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 0d188d8657..b4f80b9572 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -184,19 +184,19 @@ private enum ContactListNodeEntry: Comparable, Identifiable { if let _ = peer as? TelegramUser { status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat) } else if let group = peer as? TelegramGroup { - status = .custom(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), multiline: false) + status = .custom(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), multiline: false, isActive: false, icon: nil) } else if let channel = peer as? TelegramChannel { if case .group = channel.info { if let participantCount = participantCount, participantCount != 0 { - status = .custom(string: strings.Conversation_StatusMembers(participantCount), multiline: false) + status = .custom(string: strings.Conversation_StatusMembers(participantCount), multiline: false, isActive: false, icon: nil) } else { - status = .custom(string: strings.Group_Status, multiline: false) + status = .custom(string: strings.Group_Status, multiline: false, isActive: false, icon: nil) } } else { if let participantCount = participantCount, participantCount != 0 { - status = .custom(string: strings.Conversation_StatusSubscribers(participantCount), multiline: false) + status = .custom(string: strings.Conversation_StatusSubscribers(participantCount), multiline: false, isActive: false, icon: nil) } else { - status = .custom(string: strings.Channel_Status, multiline: false) + status = .custom(string: strings.Channel_Status, multiline: false, isActive: false, icon: nil) } } } else { @@ -1267,7 +1267,7 @@ public final class ContactListNode: ASDisplayNode { return context.engine.data.get(EngineDataMap( view.entries.compactMap { entry -> EnginePeer.Id? in switch entry { - case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _, _): if let peer = renderedPeer.peer { if let channel = peer as? TelegramChannel, case .group = channel.info { return peer.id @@ -1283,7 +1283,7 @@ public final class ContactListNode: ASDisplayNode { var peers: [(EnginePeer, Int32)] = [] for entry in view.entries { switch entry { - case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _, _): if let peer = renderedPeer.peer { if peer is TelegramGroup { peers.append((EnginePeer(peer), 0)) diff --git a/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift b/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift index faba0442e6..7d84d35f65 100644 --- a/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift @@ -52,7 +52,7 @@ private enum InviteContactsEntry: Comparable, Identifiable { case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder): let status: ContactsPeerItemStatus if count != 0 { - status = .custom(string: strings.Contacts_ImportersCount(count), multiline: false) + status = .custom(string: strings.Contacts_ImportersCount(count), multiline: false, isActive: false, icon: nil) } else { status = .none } diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index b41df530a4..82968e446c 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -31,10 +31,14 @@ public final class ContactItemHighlighting { } public enum ContactsPeerItemStatus { + public enum Icon { + case autoremove + } + case none case presence(EnginePeer.Presence, PresentationDateTimeFormat) case addressName(String) - case custom(string: String, multiline: Bool) + case custom(string: String, multiline: Bool, isActive: Bool, icon: Icon?) } public enum ContactsPeerItemSelection: Equatable { @@ -395,6 +399,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { private var credibilityIconView: ComponentHostView? private var credibilityIconComponent: EmojiStatusComponent? private let statusNode: TextNode + private var statusIconNode: ASImageNode? private var badgeBackgroundNode: ASImageNode? private var badgeTextNode: TextNode? private var selectionNode: CheckNode? @@ -733,6 +738,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { var titleAttributedString: NSAttributedString? var statusAttributedString: NSAttributedString? + var statusIcon: ContactsPeerItemStatus.Icon? + var statusIsActive: Bool = false var multilineStatus: Bool = false var userPresence: EnginePeer.Presence? @@ -801,8 +808,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } else if !suffix.isEmpty { statusAttributedString = NSAttributedString(string: suffix, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) } - case let .custom(text, multiline): - statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) + case let .custom(text, multiline, isActive, icon): + statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor) + statusIcon = icon + statusIsActive = isActive multilineStatus = multiline } } @@ -824,9 +833,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } switch item.status { - case let .custom(text, multiline): - statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) + case let .custom(text, multiline, isActive, icon): + statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor) multilineStatus = multiline + statusIsActive = isActive + statusIcon = icon default: break } @@ -882,7 +893,20 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - additionalTitleInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - badgeSize), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + var maxStatusWidth: CGFloat = params.width - leftInset - rightInset - badgeSize + if let _ = statusIcon { + maxStatusWidth -= 10.0 + } + + let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, maxStatusWidth), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + var statusIconImage: UIImage? + if let statusIcon = statusIcon { + switch statusIcon { + case .autoremove: + statusIconImage = PresentationResourcesChatList.statusAutoremoveIcon(item.presentationData.theme, isActive: statusIsActive) + } + } let titleVerticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0 let verticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0 @@ -1097,12 +1121,32 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { strongSelf.statusNode.alpha = item.enabled ? 1.0 : 1.0 let _ = statusApply() - let statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY - 1.0), size: statusLayout.size) + var statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY - 1.0), size: statusLayout.size) + if let statusIconImage { + statusFrame.origin.x += statusIconImage.size.width + 1.0 + } let previousStatusFrame = strongSelf.statusNode.frame strongSelf.statusNode.frame = statusFrame transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0)) + if let statusIconImage { + let statusIconNode: ASImageNode + if let current = strongSelf.statusIconNode { + statusIconNode = current + } else { + statusIconNode = ASImageNode() + strongSelf.statusNode.addSubnode(statusIconNode) + } + statusIconNode.image = statusIconImage + statusIconNode.frame = CGRect(origin: CGPoint(x: -statusIconImage.size.width - 1.0, y: floor((statusFrame.height - statusIconImage.size.height) / 2.0) + 1.0), size: statusIconImage.size) + } else { + if let statusIconNode = strongSelf.statusIconNode { + strongSelf.statusIconNode = nil + statusIconNode.removeFromSupernode() + } + } + if let credibilityIcon = credibilityIcon { let animationCache = item.context.animationCache let animationRenderer = item.context.animationRenderer @@ -1334,6 +1378,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { var statusFrame = self.statusNode.frame let previousStatusFrame = statusFrame statusFrame.origin.x = leftInset + offset + if let statusIconImage = self.statusIconNode?.image { + statusFrame.origin.x += statusIconImage.size.width + 1.0 + } self.statusNode.frame = statusFrame transition.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0)) diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 4e76219f3c..58e55b3238 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -103,6 +103,7 @@ public final class ContextMenuActionItem { public let icon: (PresentationTheme) -> UIImage? public let iconSource: ContextMenuActionItemIconSource? public let textIcon: (PresentationTheme) -> UIImage? + public let textLinkAction: () -> Void public let action: ((Action) -> Void)? convenience public init( @@ -116,6 +117,7 @@ public final class ContextMenuActionItem { icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil }, + textLinkAction: @escaping () -> Void = {}, action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)? ) { self.init( @@ -129,6 +131,7 @@ public final class ContextMenuActionItem { icon: icon, iconSource: iconSource, textIcon: textIcon, + textLinkAction: textLinkAction, action: action.flatMap { action in return { impl in action(impl.controller, impl.dismissWithResult) @@ -148,6 +151,7 @@ public final class ContextMenuActionItem { icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil }, + textLinkAction: @escaping () -> Void = {}, action: ((Action) -> Void)? ) { self.id = id @@ -160,6 +164,7 @@ public final class ContextMenuActionItem { self.icon = icon self.iconSource = iconSource self.textIcon = textIcon + self.textLinkAction = textLinkAction self.action = action } } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 87fb8e41ef..d47d3fd15a 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -85,7 +85,6 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin self.titleLabelNode = ImmediateTextNode() self.titleLabelNode.isAccessibilityElement = false self.titleLabelNode.displaysAsynchronously = false - self.titleLabelNode.isUserInteractionEnabled = false self.subtitleNode = ImmediateTextNode() self.subtitleNode.isAccessibilityElement = false @@ -160,6 +159,16 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin self.pressed() } + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.titleLabelNode.tapAttributeAction != nil { + if let result = self.titleLabelNode.hitTest(self.view.convert(point, to: self.titleLabelNode.view), with: event) { + return result + } + } + + return super.hitTest(point, with: event) + } + func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) { let sideInset: CGFloat = 16.0 let verticalInset: CGFloat = 11.0 @@ -219,10 +228,23 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin body: MarkdownAttributeSet(font: titleFont, textColor: titleColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: titleColor), link: MarkdownAttributeSet(font: titleBoldFont, textColor: presentationData.theme.list.itemAccentColor), - linkAttribute: { _ in return nil } + linkAttribute: { value in return ("URL", value) } ) ) self.titleLabelNode.attributedText = attributedText + self.titleLabelNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.5) + self.titleLabelNode.highlightAttributeAction = { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + } + self.titleLabelNode.tapAttributeAction = { [weak item] attributes, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + item?.textLinkAction() + } + } } else { self.titleLabelNode.attributedText = NSAttributedString( string: self.item.text, @@ -230,6 +252,8 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin textColor: titleColor) } + self.titleLabelNode.isUserInteractionEnabled = self.titleLabelNode.tapAttributeAction != nil + self.subtitleNode.attributedText = subtitle.flatMap { subtitle in return NSAttributedString( string: subtitle, diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 46aa156ee5..6115622c58 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -626,7 +626,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } case let .reference(reference): if let transitionInfo = reference.transitionInfo() { - contentRect = convertFrame(transitionInfo.referenceView.bounds, from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0) + contentRect = convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0) contentRect.size.width += 5.0 contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minX), size: CGSize(width: layout.size.width, height: contentRect.height)) } else { @@ -1040,7 +1040,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) - currentContentScreenFrame = convertFrame(putBackInfo.referenceView.bounds, from: putBackInfo.referenceView, to: self.view) + currentContentScreenFrame = convertFrame(putBackInfo.referenceView.bounds.inset(by: putBackInfo.insets), from: putBackInfo.referenceView, to: self.view) } else { return } diff --git a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift index 30ba660d9b..be05f8597b 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift @@ -13,6 +13,7 @@ public enum ItemListDisclosureItemTitleColor { public enum ItemListDisclosureStyle { case arrow + case optionArrows case none } @@ -116,7 +117,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { let iconNode: ASImageNode let titleNode: TextNode - let labelNode: TextNode + public let labelNode: TextNode let arrowNode: ASImageNode let labelBadgeNode: ASImageNode let labelImageNode: ASImageNode @@ -198,6 +199,17 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { } } + public func updateHasContextMenu(hasContextMenu: Bool) { + let transition: ContainedViewLayoutTransition + if hasContextMenu { + transition = .immediate + } else { + transition = .animated(duration: 0.3, curve: .easeInOut) + } + transition.updateAlpha(node: self.labelNode, alpha: hasContextMenu ? 0.5 : 1.0) + transition.updateAlpha(node: self.arrowNode, alpha: hasContextMenu ? 0.5 : 1.0) + } + public func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode) @@ -213,6 +225,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { rightInset = 16.0 + params.rightInset case .arrow: rightInset = 34.0 + params.rightInset + case .optionArrows: + rightInset = 34.0 + params.rightInset } var updateArrowImage: UIImage? @@ -245,7 +259,12 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { let badgeDiameter: CGFloat = 20.0 if currentItem?.presentationData.theme !== item.presentationData.theme { updatedTheme = item.presentationData.theme - updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme) + switch item.disclosureStyle { + case .none, .arrow: + updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme) + case .optionArrows: + updateArrowImage = PresentationResourcesItemList.disclosureOptionArrowsImage(item.presentationData.theme) + } if let badgeColor = badgeColor { updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor) } @@ -512,14 +531,21 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { } if let arrowImage = strongSelf.arrowNode.image { - strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size) + let arrowRightOffset: CGFloat + switch item.disclosureStyle { + case .optionArrows: + arrowRightOffset = 18.0 + case .none, .arrow: + arrowRightOffset = 7.0 + } + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - arrowRightOffset - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size) } switch item.disclosureStyle { - case .none: - strongSelf.arrowNode.isHidden = true - case .arrow: - strongSelf.arrowNode.isHidden = false + case .none: + strongSelf.arrowNode.isHidden = true + case .arrow, .optionArrows: + strongSelf.arrowNode.isHidden = false } strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel)) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 703b0ea734..029fbf5497 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -159,7 +159,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable { case let .participant(participant, label, revealActions, revealed, enabled): let status: ContactsPeerItemStatus if let label = label { - status = .custom(string: label, multiline: false) + status = .custom(string: label, multiline: false, isActive: false, icon: nil) } else if let presence = participant.presences[participant.peer.id], self.addIcon { status = .presence(EnginePeer.Presence(presence), dateTimeFormat) } else { diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index 0e401dd704..e77c6778e6 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -130,7 +130,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { case let .peer(_, participant, editing, label, enabled, isChannel, isContact): let status: ContactsPeerItemStatus if let label = label { - status = .custom(string: label, multiline: false) + status = .custom(string: label, multiline: false, isActive: false, icon: nil) } else if participant.peer.id != context.account.peerId { let presence = participant.presences[participant.peer.id] ?? TelegramUserPresence(status: .none, lastActivity: 0) status = .presence(EnginePeer.Presence(presence), presentationData.dateTimeFormat) diff --git a/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift b/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift index 8cabf9bccb..c9318435d1 100644 --- a/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift +++ b/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift @@ -138,10 +138,10 @@ class ItemListSecretChatKeyItemNode: ListViewItemNode { return { item, params, neighbors in let rightInset: CGFloat switch item.disclosureStyle { - case .none: - rightInset = 16.0 + params.rightInset - case .arrow: - rightInset = 34.0 + params.rightInset + case .none: + rightInset = 16.0 + params.rightInset + case .arrow, .optionArrows: + rightInset = 34.0 + params.rightInset } var updateArrowImage: UIImage? @@ -279,7 +279,7 @@ class ItemListSecretChatKeyItemNode: ListViewItemNode { switch item.disclosureStyle { case .none: strongSelf.arrowNode.isHidden = true - case .arrow: + case .arrow, .optionArrows: strongSelf.arrowNode.isHidden = false } diff --git a/submodules/PeerInfoUI/Sources/OldChannelsController.swift b/submodules/PeerInfoUI/Sources/OldChannelsController.swift index 9b01607fbd..1ed188000e 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsController.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsController.swift @@ -173,7 +173,7 @@ private enum OldChannelsEntry: ItemListNodeEntry { case let .peersHeader(title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .peer(_, peer, selected): - return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in + return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in arguments.togglePeer(peer.peer.id, true) }, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil) } diff --git a/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift b/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift index 776bf8875c..903b05bc1a 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift @@ -141,7 +141,7 @@ private enum OldChannelsSearchEntry: Comparable, Identifiable { func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem { switch self { case let .peer(_, peer, selected): - return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in + return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in interaction.togglePeer(peer.peer.id) }, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil) } diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index f30758000d..4c7ee4a4ae 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -111,12 +111,12 @@ public struct ChatListForumTopicData: Equatable { } public enum ChatListEntry: Comparable { - case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailed: Bool, isContact: Bool) + case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailed: Bool, isContact: Bool, autoremoveTimeout: Int32?) case HoleEntry(ChatListHole) public var index: ChatListIndex { switch self { - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): return index case let .HoleEntry(hole): return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) @@ -125,9 +125,9 @@ public enum ChatListEntry: Comparable { public static func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool { switch lhs { - case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsForumTopicData, lhsTopForumTopics, lhsHasFailed, lhsIsContact): + case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsForumTopicData, lhsTopForumTopics, lhsHasFailed, lhsIsContact, lhsAutoremoveTimeout): switch rhs { - case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsForumTopicData, rhsTopForumTopics, rhsHasFailed, rhsIsContact): + case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsForumTopicData, rhsTopForumTopics, rhsHasFailed, rhsIsContact, rhsAutoremoveTimeout): if lhsIndex != rhsIndex { return false } @@ -177,6 +177,9 @@ public enum ChatListEntry: Comparable { if lhsIsContact != rhsIsContact { return false } + if lhsAutoremoveTimeout != rhsAutoremoveTimeout { + return false + } return true default: return false @@ -197,7 +200,7 @@ public enum ChatListEntry: Comparable { enum MutableChatListEntry: Equatable { case IntermediateMessageEntry(index: ChatListIndex, messageIndex: MessageIndex?) - case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailedMessages: Bool, isContact: Bool) + case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailedMessages: Bool, isContact: Bool, autoremoveTimeout: Int32?) case HoleEntry(ChatListHole) init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) { @@ -213,7 +216,7 @@ enum MutableChatListEntry: Equatable { switch self { case let .IntermediateMessageEntry(index, _): return index - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _): return index case let .HoleEntry(hole): return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) @@ -713,7 +716,12 @@ final class MutableChatListView { } } - return .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact) + var autoremoveTimeout: Int32? + if let cachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId) { + autoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData) + } + + return .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact, autoremoveTimeout: autoremoveTimeout) default: return nil } @@ -742,8 +750,8 @@ public final class ChatListView { var entries: [ChatListEntry] = [] for entry in mutableView.sampledState.entries { switch entry { - case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact): - entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact)) + case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact, autoremoveTimeout): + entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact, autoremoveTimeout: autoremoveTimeout)) case let .HoleEntry(hole): entries.append(.HoleEntry(hole)) case .IntermediateMessageEntry: @@ -760,9 +768,9 @@ public final class ChatListView { var additionalItemEntries: [ChatListAdditionalItemEntry] = [] for entry in mutableView.additionalItemEntries { switch entry.entry { - case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact): + case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact, autoremoveTimeout): additionalItemEntries.append(ChatListAdditionalItemEntry( - entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact), + entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact, autoremoveTimeout: autoremoveTimeout), info: entry.info )) case .HoleEntry: diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index 57c7e0ec0e..ea2f2f63ea 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -515,7 +515,7 @@ private final class ChatListViewSpaceState { let entryPeer: Peer let entryNotificationsPeerId: PeerId switch entry { - case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _, _, _, _): if let peer = renderedPeer.peer { entryPeer = peer entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id @@ -630,13 +630,13 @@ private final class ChatListViewSpaceState { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact): + case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact, autoremoveTimeout): if let peer = renderedPeer.peer { let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id if let (_, updated) = transaction.currentUpdatedPeerNotificationSettings[notificationsPeerId] { let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: updated) - return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, isContact: isContact) + return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, isContact: isContact, autoremoveTimeout: autoremoveTimeout) } else { return nil } @@ -654,7 +654,7 @@ private final class ChatListViewSpaceState { if !transaction.updatedFailedMessagePeerIds.isEmpty { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, _, isContact): + case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, _, isContact, autoremoveTimeout): if transaction.updatedFailedMessagePeerIds.contains(index.messageIndex.id.peerId) { return .MessageEntry( index: index, @@ -669,7 +669,8 @@ private final class ChatListViewSpaceState { forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), - isContact: isContact + isContact: isContact, + autoremoveTimeout: autoremoveTimeout ) } else { return nil @@ -685,7 +686,7 @@ private final class ChatListViewSpaceState { if !transaction.currentUpdatedPeers.isEmpty { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact): + case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact, autoremoveTimeout): var updatedMessages: [Message] = messages var hasUpdatedMessages = false for i in 0 ..< updatedMessages.count { @@ -710,7 +711,9 @@ private final class ChatListViewSpaceState { forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, - isContact: isContact) + isContact: isContact, + autoremoveTimeout: autoremoveTimeout + ) } else { return nil } @@ -725,7 +728,7 @@ private final class ChatListViewSpaceState { if !transaction.currentUpdatedPeerPresences.isEmpty { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact): + case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact, autoremoveTimeout): var presencePeerId = entryRenderedPeer.peerId if let peer = entryRenderedPeer.peers[entryRenderedPeer.peerId], let associatedPeerId = peer.associatedPeerId { presencePeerId = associatedPeerId @@ -744,7 +747,8 @@ private final class ChatListViewSpaceState { forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, - isContact: isContact + isContact: isContact, + autoremoveTimeout: autoremoveTimeout ) } else { return nil @@ -763,7 +767,7 @@ private final class ChatListViewSpaceState { let entryPeer: Peer let entryNotificationsPeerId: PeerId switch entry { - case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _, _, _, _): if let peer = entryRenderedPeer.peer { entryPeer = peer entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id @@ -881,10 +885,20 @@ private final class ChatListViewSpaceState { } } - if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty { + var cachedPeerDataUpdated = false + if !transaction.currentUpdatedCachedPeerData.isEmpty { + let _ = self.orderedEntries.mutableScan { entry in + if transaction.currentUpdatedCachedPeerData[entry.index.messageIndex.id.peerId] != nil { + cachedPeerDataUpdated = true + } + return nil + } + } + + if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty || cachedPeerDataUpdated { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact): + case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact, autoremoveTimeout): var updatedChatListMessageTagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = tagSummaryInfo var didUpdateSummaryInfo = false @@ -938,6 +952,14 @@ private final class ChatListViewSpaceState { didUpdateSummaryInfo = true } + var updatedAutoremoveTimeout: Int32? + if let cachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId) { + updatedAutoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData) + if updatedAutoremoveTimeout != autoremoveTimeout { + didUpdateSummaryInfo = true + } + } + if didUpdateSummaryInfo { return .MessageEntry( index: index, @@ -952,7 +974,8 @@ private final class ChatListViewSpaceState { forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, - isContact: isContact + isContact: isContact, + autoremoveTimeout: updatedAutoremoveTimeout ) } else { return nil @@ -1086,7 +1109,7 @@ private extension MutableChatListEntry { switch self { case let .IntermediateMessageEntry(index, _): return index.messageIndex.id.peerId - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _): return index.messageIndex.id.peerId case .HoleEntry: return nil @@ -1097,7 +1120,7 @@ private extension MutableChatListEntry { switch self { case let .IntermediateMessageEntry(index, _): return MutableChatListEntryIndex(index: index, isMessage: true) - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _): return MutableChatListEntryIndex(index: index, isMessage: true) case let .HoleEntry(hole): return MutableChatListEntryIndex(index: ChatListIndex(pinningIndex: nil, messageIndex: hole.index), isMessage: false) @@ -1108,7 +1131,7 @@ private extension MutableChatListEntry { switch self { case let .IntermediateMessageEntry(index, _): return .peer(index.messageIndex.id.peerId) - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _): return .peer(index.messageIndex.id.peerId) case let .HoleEntry(hole): return .hole(hole.index) @@ -1558,7 +1581,12 @@ struct ChatListViewState { } } - let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId)) + var autoremoveTimeout: Int32? + if let cachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId) { + autoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData) + } + + let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId), autoremoveTimeout: autoremoveTimeout) if directionIndex == 0 { self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry) } else { diff --git a/submodules/Postbox/Sources/SeedConfiguration.swift b/submodules/Postbox/Sources/SeedConfiguration.swift index a189187b97..f39902dd77 100644 --- a/submodules/Postbox/Sources/SeedConfiguration.swift +++ b/submodules/Postbox/Sources/SeedConfiguration.swift @@ -75,6 +75,7 @@ public final class SeedConfiguration { public let defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings public let mergeMessageAttributes: ([MessageAttribute], inout [MessageAttribute]) -> Void public let decodeMessageThreadInfo: (CodableEntry) -> Message.AssociatedThreadInfo? + public let decodeAutoremoveTimeout: (CachedPeerData) -> Int32? public init( globalMessageIdsPeerIdNamespaces: Set, @@ -99,7 +100,8 @@ public final class SeedConfiguration { getGlobalNotificationSettings: @escaping (Transaction) -> PostboxGlobalNotificationSettings?, defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings, mergeMessageAttributes: @escaping ([MessageAttribute], inout [MessageAttribute]) -> Void, - decodeMessageThreadInfo: @escaping (CodableEntry) -> Message.AssociatedThreadInfo? + decodeMessageThreadInfo: @escaping (CodableEntry) -> Message.AssociatedThreadInfo?, + decodeAutoremoveTimeout: @escaping (CachedPeerData) -> Int32? ) { self.globalMessageIdsPeerIdNamespaces = globalMessageIdsPeerIdNamespaces self.initializeChatListWithHole = initializeChatListWithHole @@ -120,5 +122,6 @@ public final class SeedConfiguration { self.defaultGlobalNotificationSettings = defaultGlobalNotificationSettings self.mergeMessageAttributes = mergeMessageAttributes self.decodeMessageThreadInfo = decodeMessageThreadInfo + self.decodeAutoremoveTimeout = decodeAutoremoveTimeout } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift new file mode 100644 index 0000000000..56c77c4572 --- /dev/null +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift @@ -0,0 +1,109 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import ItemListUI +import PresentationDataUtils +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import AccountContext + +class GlobalAutoremoveHeaderItem: ListViewItem, ItemListItem { + let context: AccountContext + let theme: PresentationTheme + let sectionId: ItemListSectionId + + init(context: AccountContext, theme: PresentationTheme, sectionId: ItemListSectionId) { + self.context = context + self.theme = theme + self.sectionId = sectionId + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = GlobalAutoremoveHeaderItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + guard let nodeValue = node() as? GlobalAutoremoveHeaderItemNode else { + assertionFailure() + return + } + + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } +} + +private let titleFont = Font.regular(13.0) + +class GlobalAutoremoveHeaderItemNode: ListViewItemNode { + private var animationNode: AnimatedStickerNode + + private var item: GlobalAutoremoveHeaderItem? + + init() { + self.animationNode = DefaultAnimatedStickerNodeImpl() + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.animationNode) + } + + func asyncLayout() -> (_ item: GlobalAutoremoveHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + return { item, params, neighbors in + //let leftInset: CGFloat = 32.0 + params.leftInset + let topInset: CGFloat = 92.0 + + let contentSize = CGSize(width: params.width, height: topInset) + let insets = itemListNeighborsGroupedInsets(neighbors, params) + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (layout, { [weak self] in + if let strongSelf = self { + if strongSelf.item == nil { + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "GlobalAutoRemove"), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + strongSelf.animationNode.visibility = true + } + strongSelf.item = item + + let iconSize = CGSize(width: 96.0, height: 96.0) + strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize) + strongSelf.animationNode.updateLayout(size: iconSize) + } + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } +} diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift new file mode 100644 index 0000000000..80f52e4639 --- /dev/null +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift @@ -0,0 +1,407 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import PresentationDataUtils +import AccountContext +import AuthTransferUI +import ItemListPeerActionItem +import DeviceAccess +import QrCodeUI +import ChatTimerScreen +import UndoUI + +private final class GlobalAutoremoveScreenArguments { + let context: AccountContext + let updateValue: (Int32) -> Void + let openCustomValue: () -> Void + let infoLinkAction: () -> Void + + init( + context: AccountContext, + updateValue: @escaping (Int32) -> Void, + openCustomValue: @escaping () -> Void, + infoLinkAction: @escaping () -> Void + ) { + self.context = context + self.updateValue = updateValue + self.openCustomValue = openCustomValue + self.infoLinkAction = infoLinkAction + } +} + +private enum GlobalAutoremoveSection: Int32 { + case header + case general +} + +private enum GlobalAutoremoveEntry: ItemListNodeEntry { + case header + case sectionHeader(String) + case timerOption(value: Int32, text: String, isSelected: Bool) + case customAction(String) + case info(String) + + var section: ItemListSectionId { + switch self { + case .header: + return GlobalAutoremoveSection.header.rawValue + case .sectionHeader, .timerOption, .customAction, .info: + return GlobalAutoremoveSection.general.rawValue + } + } + + var stableId: Int { + return self.sortIndex + } + + var sortIndex: Int { + switch self { + case .header: + return 0 + case .sectionHeader: + return 1 + case let .timerOption(value, _, _): + return 1000 + Int(value) + case .customAction: + return Int.max - 1000 + 0 + case .info: + return Int.max - 1000 + 1 + } + } + + static func ==(lhs: GlobalAutoremoveEntry, rhs: GlobalAutoremoveEntry) -> Bool { + switch lhs { + case .header: + if case .header = rhs { + return true + } else { + return false + } + case let .sectionHeader(text): + if case .sectionHeader(text) = rhs { + return true + } else { + return false + } + case let .timerOption(value, text, isSelected): + if case .timerOption(value, text, isSelected) = rhs { + return true + } else { + return false + } + case let .customAction(text): + if case .customAction(text) = rhs { + return true + } else { + return false + } + case let .info(text): + if case .info(text) = rhs { + return true + } else { + return false + } + } + } + + static func <(lhs: GlobalAutoremoveEntry, rhs: GlobalAutoremoveEntry) -> Bool { + return lhs.sortIndex < rhs.sortIndex + } + + func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { + let arguments = arguments as! GlobalAutoremoveScreenArguments + switch self { + case .header: + return GlobalAutoremoveHeaderItem(context: arguments.context, theme: presentationData.theme, sectionId: self.section) + case let .sectionHeader(text): + return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) + case let .timerOption(value, text, isSelected): + return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.updateValue(value) + }) + case let .customAction(text): + return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + arguments.openCustomValue() + }) + case let .info(text): + return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in + arguments.infoLinkAction() + }) + } + } +} + +private struct GlobalAutoremoveScreenState: Equatable { + var additionalValues: Set + var updatedValue: Int32 +} + +private func globalAutoremoveScreenEntries(presentationData: PresentationData, state: GlobalAutoremoveScreenState) -> [GlobalAutoremoveEntry] { + var entries: [GlobalAutoremoveEntry] = [] + + entries.append(.header) + + let effectiveCurrentValue = state.updatedValue + + //TODO:localize + entries.append(.sectionHeader("SELF-DESTRUCT TIMER")) + + var values: [Int32] = [ + 0, + 1 * 24 * 60 * 60, + 7 * 24 * 60 * 60, + 31 * 24 * 60 * 60 + ] + + if !values.contains(effectiveCurrentValue) { + values.append(effectiveCurrentValue) + } + for value in state.additionalValues { + if !values.contains(value) { + values.append(value) + } + } + + values.sort() + + //TODO:localize + for value in values { + let text: String + if value == 0 { + text = "Off" + } else { + text = "After \(timeIntervalString(strings: presentationData.strings, value: value))" + } + entries.append(.timerOption(value: value, text: text, isSelected: effectiveCurrentValue == value)) + } + + entries.append(.customAction("Set Custom Time...")) + + //TODO:localize + if effectiveCurrentValue == 0 { + entries.append(.info("If enabled, all new messages in chats you start will be automatically deleted for everyone at some point after they have been sent. This will not affect your existing chats.")) + } else { + entries.append(.info("All new messages in chats you started will be automatically deleted for everyone 3 weeks after they have been sent. You can also [apply this setting for your existing chats]().")) + } + + return entries +} + +public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, updated: @escaping (Int32) -> Void) -> ViewController { + let initialState = GlobalAutoremoveScreenState( + additionalValues: Set([initialValue]), + updatedValue: initialValue + ) + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((GlobalAutoremoveScreenState) -> GlobalAutoremoveScreenState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + + var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + var pushControllerImpl: ((ViewController) -> Void)? + var dismissImpl: (() -> Void)? + var getController: (() -> ViewController?)? + + let _ = dismissImpl + let _ = pushControllerImpl + let _ = presentControllerImpl + let _ = updateState + + let actionsDisposable = DisposableSet() + + let updateTimeoutDisposable = MetaDisposable() + actionsDisposable.add(updateTimeoutDisposable) + + let updateValue: (Int32) -> Void = { timeout in + let apply: (Int32) -> Void = { timeout in + updateState { state in + var state = state + state.updatedValue = timeout + if timeout != 0 { + state.additionalValues.insert(timeout) + } + return state + } + + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + + var isOn: Bool = true + var text: String? + if timeout != 0 { + text = "Messages in all new chats you start will be automatically deleted after \(timeIntervalString(strings: presentationData.strings, value: timeout))." + } else { + isOn = false + text = "Messages in all new chats you start will not be automatically deleted."; + } + if let text = text { + var animateAsReplacement = false + if let window = getController?()?.window { + window.forEachController { other in + if let other = other as? UndoOverlayController { + animateAsReplacement = true + other.dismiss() + } + } + } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: isOn, title: nil, text: text), elevatedLayout: false, animateInAsReplacement: animateAsReplacement, action: { _ in return false }), nil) + } + + updateTimeoutDisposable.set((context.engine.privacy.updateGlobalMessageRemovalTimeout(timeout: timeout == 0 ? nil : timeout) + |> deliverOnMainQueue).start(completed: { + updated(timeout) + })) + } + if timeout == 0 || stateValue.with({ $0 }).updatedValue != 0 { + apply(timeout) + } else { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + //TODO:localize + let valueText = timeIntervalString(strings: presentationData.strings, value: timeout) + presentControllerImpl?(standardTextAlertController( + theme: AlertControllerTheme(presentationData: presentationData), + title: "Self-Destruct Timer", + text: "Are you sure you want all messages in new chats started by you to be automatically deleted for everyone \(valueText) after they have been sent?", + actions: [ + TextAlertAction(type: .defaultAction, title: "Enable Auto-Deletion", action: { + apply(timeout) + }), + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}) + ], + actionLayout: .vertical + ), nil) + } + } + + let arguments = GlobalAutoremoveScreenArguments( + context: context, + updateValue: { value in + updateValue(value) + }, + openCustomValue: { + let currentValue = stateValue.with({ $0 }).updatedValue + let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: currentValue == 0 ? nil : currentValue, dismissByTapOutside: true, completion: { value in + updateValue(value) + }) + presentControllerImpl?(controller, nil) + }, + infoLinkAction: { + let value = stateValue.with({ $0 }).updatedValue + if value == 0 { + return + } + + //TODO:localize + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let valueText = timeIntervalString(strings: presentationData.strings, value: value) + + //TODO:localize + let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams( + context: context, + mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection( + title: "Select Chats", + searchPlaceholder: "Select chats to apply the \(valueText) self-destruct timer", + selectedChats: Set(), + additionalCategories: nil, + chatListFilters: nil, + displayAutoremoveTimeout: true + )), + options: [], + filters: [.excludeSelf], + isPeerEnabled: { peer in + var canManage = false + if case let .user(user) = peer { + if user.botInfo == nil { + canManage = true + } + } else if case .secretChat = peer { + canManage = true + } else if case let .legacyGroup(group) = peer { + canManage = !group.hasBannedPermission(.banChangeInfo) + } else if case let .channel(channel) = peer { + canManage = channel.hasPermission(.changeInfo) + } + return canManage + } + )) + selectionController.navigationPresentation = .modal + + let _ = (selectionController.result + |> take(1) + |> deliverOnMainQueue).start(next: { [weak selectionController] result in + var contacts: [ContactListPeerId] = [] + if case let .result(peerIdsValue, _) = result { + contacts = peerIdsValue + } + let peerIds = contacts.compactMap { item -> EnginePeer.Id? in + switch item { + case let .peer(id): + return id + case .deviceContact: + return nil + } + } + + if peerIds.isEmpty { + selectionController?.dismiss() + } else { + selectionController?.displayProgress = true + let _ = (context.engine.peers.setChatMessageAutoremoveTimeouts(peerIds: peerIds, timeout: value) + |> deliverOnMainQueue).start(completed: { + selectionController?.dismiss() + }) + } + }) + + pushControllerImpl?(selectionController) + } + ) + + let signal = combineLatest(queue: .mainQueue(), + context.sharedContext.presentationData, + statePromise.get() + ) + |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in + let rightNavigationButton: ItemListNavigationButton? = nil + + //TODO:localize + let title: ItemListControllerTitle = .text("Auto-Delete Messages") + + let entries: [GlobalAutoremoveEntry] = globalAutoremoveScreenEntries(presentationData: presentationData, state: state) + + let animateChanges = false + + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges, scrollEnabled: true) + + return (controllerState, (listState, arguments)) + } + |> afterDisposed { + actionsDisposable.dispose() + } + + let controller = ItemListController(context: context, state: signal) + getController = { [weak controller] in + return controller + } + presentControllerImpl = { [weak controller] c, p in + guard let controller else { + return + } + controller.present(c, in: .window(.root), with: p) + } + pushControllerImpl = { [weak controller] c in + controller?.push(c) + } + dismissImpl = { [weak controller] in + controller?.dismiss() + } + + return controller +} diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index d355f62b3e..e451690eb8 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -105,7 +105,6 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case accountHeader(PresentationTheme, String) case accountTimeout(PresentationTheme, String, String) case accountInfo(PresentationTheme, String) - case messageAutoremoveHeader(PresentationTheme, String) case messageAutoremoveTimeout(PresentationTheme, String, String) case messageAutoremoveInfo(PresentationTheme, String) case dataSettings(PresentationTheme, String) @@ -113,18 +112,16 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { var section: ItemListSectionId { switch self { - case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail, .loginEmailInfo: - return PrivacyAndSecuritySection.general.rawValue - case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo: - return PrivacyAndSecuritySection.privacy.rawValue - case .autoArchiveHeader, .autoArchive, .autoArchiveInfo: - return PrivacyAndSecuritySection.autoArchive.rawValue - case .accountHeader, .accountTimeout, .accountInfo: - return PrivacyAndSecuritySection.account.rawValue - case .messageAutoremoveHeader, .messageAutoremoveTimeout, .messageAutoremoveInfo: - return PrivacyAndSecuritySection.messageAutoremove.rawValue - case .dataSettings, .dataSettingsInfo: - return PrivacyAndSecuritySection.dataSettings.rawValue + case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail, .loginEmailInfo, .messageAutoremoveTimeout, .messageAutoremoveInfo: + return PrivacyAndSecuritySection.general.rawValue + case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo: + return PrivacyAndSecuritySection.privacy.rawValue + case .autoArchiveHeader, .autoArchive, .autoArchiveInfo: + return PrivacyAndSecuritySection.autoArchive.rawValue + case .accountHeader, .accountTimeout, .accountInfo: + return PrivacyAndSecuritySection.account.rawValue + case .dataSettings, .dataSettingsInfo: + return PrivacyAndSecuritySection.dataSettings.rawValue } } @@ -138,50 +135,48 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return 3 case .twoStepVerification: return 4 - case .loginEmail: - return 5 - case .loginEmailInfo: - return 6 - case .privacyHeader: - return 7 - case .phoneNumberPrivacy: - return 8 - case .lastSeenPrivacy: - return 9 - case .profilePhotoPrivacy: - return 10 - case .voiceCallPrivacy: - return 11 - case .voiceMessagePrivacy: - return 12 - case .forwardPrivacy: - return 13 - case .groupPrivacy: - return 14 - case .selectivePrivacyInfo: - return 15 - case .autoArchiveHeader: - return 16 - case .autoArchive: - return 17 - case .autoArchiveInfo: - return 18 - case .accountHeader: - return 19 - case .accountTimeout: - return 20 - case .accountInfo: - return 21 - case .messageAutoremoveHeader: - return 22 case .messageAutoremoveTimeout: - return 23 + return 5 case .messageAutoremoveInfo: - return 24 + return 6 + case .loginEmail: + return 7 + case .loginEmailInfo: + return 8 + case .privacyHeader: + return 9 + case .phoneNumberPrivacy: + return 10 + case .lastSeenPrivacy: + return 11 + case .profilePhotoPrivacy: + return 12 + case .voiceCallPrivacy: + return 13 + case .voiceMessagePrivacy: + return 14 + case .forwardPrivacy: + return 15 + case .groupPrivacy: + return 16 + case .selectivePrivacyInfo: + return 17 + case .autoArchiveHeader: + return 18 + case .autoArchive: + return 19 + case .autoArchiveInfo: + return 20 + case .accountHeader: + return 21 + case .accountTimeout: + return 22 + case .accountInfo: + return 23 case .dataSettings: - return 25 + return 24 case .dataSettingsInfo: - return 26 + return 25 } } @@ -313,12 +308,6 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { } else { return false } - case let .messageAutoremoveHeader(lhsTheme, lhsText): - if case let .messageAutoremoveHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } case let .messageAutoremoveTimeout(lhsTheme, lhsText, lhsValue): if case let .messageAutoremoveTimeout(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true @@ -397,6 +386,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openTwoStepVerification(data) }) + case let .messageAutoremoveTimeout(_, text, value): + return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Timer")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + arguments.setupMessageAutoremove() + }, tag: PrivacyAndSecurityEntryTag.messageAutoremoveTimeout) + case let .messageAutoremoveInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .loginEmail(_, text, emailPattern): return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/LoginEmail")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openEmailSettings(emailPattern) @@ -423,14 +418,6 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { }, tag: PrivacyAndSecurityEntryTag.accountTimeout) case let .accountInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .messageAutoremoveHeader(_, text): - return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) - case let .messageAutoremoveTimeout(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { - arguments.setupMessageAutoremove() - }, tag: PrivacyAndSecurityEntryTag.messageAutoremoveTimeout) - case let .messageAutoremoveInfo(_, text): - return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .dataSettings(_, text): return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openDataSettings() @@ -527,6 +514,26 @@ private func privacyAndSecurityControllerEntries( } entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth, twoStepAuthString, twoStepAuthData)) + //TODO:localize + if let privacySettings = privacySettings { + let value: Int32? + if let updatingMessageAutoremoveTimeoutValue = state.updatingMessageAutoremoveTimeoutValue { + value = updatingMessageAutoremoveTimeoutValue + } else { + value = privacySettings.messageAutoremoveTimeout + } + let valueText: String + if let value { + valueText = timeIntervalString(strings: presentationData.strings, value: value) + } else { + valueText = "Off" + } + entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", valueText)) + } else { + entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", presentationData.strings.Channel_NotificationLoading)) + } + entries.append(.messageAutoremoveInfo(presentationData.theme, "Automatically delete messages for everyone after a period of time in all new chats you start.")) + if loginEmail != nil { entries.append(.loginEmail(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmail, loginEmail)) entries.append(.loginEmailInfo(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmailInfo)) @@ -588,27 +595,6 @@ private func privacyAndSecurityControllerEntries( } entries.append(.accountInfo(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountHelp)) - //TODO:localize - entries.append(.messageAutoremoveHeader(presentationData.theme, "MESSAGE TIMER")) - if let privacySettings = privacySettings { - let value: Int32? - if let updatingMessageAutoremoveTimeoutValue = state.updatingMessageAutoremoveTimeoutValue { - value = updatingMessageAutoremoveTimeoutValue - } else { - value = privacySettings.messageAutoremoveTimeout - } - let valueText: String - if let value { - valueText = timeIntervalString(strings: presentationData.strings, value: value) - } else { - valueText = "Never" - } - entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", valueText)) - } else { - entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", presentationData.strings.Channel_NotificationLoading)) - } - entries.append(.messageAutoremoveInfo(presentationData.theme, "Set a global message timer.")) - entries.append(.dataSettings(presentationData.theme, presentationData.strings.PrivacySettings_DataSettings)) entries.append(.dataSettingsInfo(presentationData.theme, presentationData.strings.PrivacySettings_DataSettingsHelp)) @@ -1067,79 +1053,21 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting let signal = privacySettingsPromise.get() |> take(1) |> deliverOnMainQueue - updateAccountTimeoutDisposable.set(signal.start(next: { [weak updateAccountTimeoutDisposable] privacySettingsValue in - if let privacySettingsValue = privacySettingsValue { - let timeoutAction: (Int32?) -> Void = { timeout in - if let updateAccountTimeoutDisposable = updateAccountTimeoutDisposable { - updateState { state in - var state = state - state.updatingMessageAutoremoveTimeoutValue = timeout - return state - } - let applyTimeout: Signal = privacySettingsPromise.get() - |> filter { $0 != nil } - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { value -> Signal in - if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: timeout))) - } - return .complete() - } - updateAccountTimeoutDisposable.set((context.engine.privacy.updateGlobalMessageRemovalTimeout(timeout: timeout) - |> then(applyTimeout) - |> deliverOnMainQueue).start(completed: { - updateState { state in - var state = state - state.updatingMessageAutoremoveTimeoutValue = nil - return state - } - })) - } - } - - let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: privacySettingsValue.messageAutoremoveTimeout, dismissByTapOutside: true, completion: { value in - timeoutAction(value) - }) - presentControllerImpl?(controller) - - /* - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = ActionSheetController(presentationData: presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - let timeoutValues: [Int32] = [ - 1 * 30 * 24 * 60 * 60, - 3 * 30 * 24 * 60 * 60, - 6 * 30 * 24 * 60 * 60, - 365 * 24 * 60 * 60 - ] - var timeoutItems: [ActionSheetItem] = timeoutValues.map { value in - return ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: value), action: { - dismissAction() - timeoutAction(value) - }) - } - timeoutItems.append(ActionSheetButtonItem(title: presentationData.strings.PrivacySettings_DeleteAccountNow, color: .destructive, action: { - dismissAction() - - guard let navigationController = getNavigationControllerImpl?() else { - return - } - - let _ = (combineLatest(twoStepAuth.get(), twoStepAuthDataValue.get()) + updateAccountTimeoutDisposable.set(signal.start(next: { privacySettingsValue in + if let privacySettingsValue { + pushControllerImpl?(globalAutoremoveScreen(context: context, initialValue: privacySettingsValue.messageAutoremoveTimeout ?? 0, updated: { updatedValue in + let applyTimeout: Signal = privacySettingsPromise.get() + |> filter { $0 != nil } |> take(1) - |> deliverOnMainQueue).start(next: { hasTwoStepAuth, twoStepAuthData in - let optionsController = deleteAccountOptionsController(context: context, navigationController: navigationController, hasTwoStepAuth: hasTwoStepAuth ?? false, twoStepAuthData: twoStepAuthData) - pushControllerImpl?(optionsController, true) - }) - })) - controller.setItemGroups([ - ActionSheetItemGroup(items: timeoutItems), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - presentControllerImpl?(controller)*/ + |> deliverOnMainQueue + |> mapToSignal { value -> Signal in + if let value = value { + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: updatedValue == 0 ? nil : updatedValue))) + } + return .complete() + } + let _ = applyTimeout.start() + }), true) } })) }, openDataSettings: { diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 6aa5b49cc3..fa47274f40 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -1028,7 +1028,7 @@ public final class ShareController: ViewController { var peers: [EngineRenderedPeer] = [] for entry in view.0.entries.reversed() { switch entry { - case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _, _): if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) { peers.append(EngineRenderedPeer(renderedPeer)) } diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 1490d54b74..360ce83835 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -1315,7 +1315,8 @@ private func threadList(context: AccountContext, peerId: EnginePeer.Id) -> Signa forumTopicData: nil, topForumTopicItems: [], hasFailed: false, - isContact: false + isContact: false, + autoremoveTimeout: nil )) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index eca711ed97..4df27e6e4a 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -181,7 +181,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2104790276] = { return Api.DataJSON.parse_dataJSON($0) } dict[414687501] = { return Api.DcOption.parse_dcOption($0) } dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) } - dict[-1460809483] = { return Api.Dialog.parse_dialog($0) } + dict[-712374074] = { return Api.Dialog.parse_dialog($0) } dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) } dict[1949890536] = { return Api.DialogFilter.parse_dialogFilter($0) } dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) } @@ -225,6 +225,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[594758406] = { return Api.EncryptedMessage.parse_encryptedMessageService($0) } dict[179611673] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) } dict[-317687113] = { return Api.ExportedChatInvite.parse_chatInvitePublicJoinRequests($0) } + dict[1103040667] = { return Api.ExportedContactToken.parse_exportedContactToken($0) } dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) } dict[-207944868] = { return Api.FileHash.parse_fileHash($0) } dict[-11252123] = { return Api.Folder.parse_folder($0) } @@ -455,7 +456,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-648257196] = { return Api.MessageAction.parse_messageActionSecureValuesSent($0) } dict[455635795] = { return Api.MessageAction.parse_messageActionSecureValuesSentMe($0) } dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } - dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } + dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } dict[-1262252875] = { return Api.MessageAction.parse_messageActionWebViewDataSent($0) } @@ -938,6 +939,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1148485274] = { return Api.auth.Authorization.parse_authorizationSignUpRequired($0) } dict[1948046307] = { return Api.auth.CodeType.parse_codeTypeCall($0) } dict[577556219] = { return Api.auth.CodeType.parse_codeTypeFlashCall($0) } + dict[116234636] = { return Api.auth.CodeType.parse_codeTypeFragmentSms($0) } dict[-702884114] = { return Api.auth.CodeType.parse_codeTypeMissedCall($0) } dict[1923290508] = { return Api.auth.CodeType.parse_codeTypeSms($0) } dict[-1271602504] = { return Api.auth.ExportedAuthorization.parse_exportedAuthorization($0) } @@ -951,6 +953,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) } dict[1511364673] = { return Api.auth.SentCodeType.parse_sentCodeTypeEmailCode($0) } dict[-1425815847] = { return Api.auth.SentCodeType.parse_sentCodeTypeFlashCall($0) } + dict[-648651719] = { return Api.auth.SentCodeType.parse_sentCodeTypeFragmentSms($0) } dict[-2113903484] = { return Api.auth.SentCodeType.parse_sentCodeTypeMissedCall($0) } dict[-1521934870] = { return Api.auth.SentCodeType.parse_sentCodeTypeSetUpEmailRequired($0) } dict[-1073693790] = { return Api.auth.SentCodeType.parse_sentCodeTypeSms($0) } @@ -1294,6 +1297,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.ExportedChatInvite: _1.serialize(buffer, boxed) + case let _1 as Api.ExportedContactToken: + _1.serialize(buffer, boxed) case let _1 as Api.ExportedMessageLink: _1.serialize(buffer, boxed) case let _1 as Api.FileHash: diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 407ec13c3d..f2b27273ff 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -1018,7 +1018,7 @@ public extension Api { case messageActionSecureValuesSent(types: [Api.SecureValueType]) case messageActionSecureValuesSentMe(values: [Api.SecureValue], credentials: Api.SecureCredentialsEncrypted) case messageActionSetChatTheme(emoticon: String) - case messageActionSetMessagesTTL(period: Int32) + case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) case messageActionWebViewDataSent(text: String) @@ -1250,11 +1250,13 @@ public extension Api { } serializeString(emoticon, buffer: buffer, boxed: false) break - case .messageActionSetMessagesTTL(let period): + case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): if boxed { - buffer.appendInt32(-1441072131) + buffer.appendInt32(1007897979) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(period, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} break case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): if boxed { @@ -1353,8 +1355,8 @@ public extension Api { return ("messageActionSecureValuesSentMe", [("values", String(describing: values)), ("credentials", String(describing: credentials))]) case .messageActionSetChatTheme(let emoticon): return ("messageActionSetChatTheme", [("emoticon", String(describing: emoticon))]) - case .messageActionSetMessagesTTL(let period): - return ("messageActionSetMessagesTTL", [("period", String(describing: period))]) + case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): + return ("messageActionSetMessagesTTL", [("flags", String(describing: flags)), ("period", String(describing: period)), ("autoSettingFrom", String(describing: autoSettingFrom))]) case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): return ("messageActionTopicCreate", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId))]) case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): @@ -1745,9 +1747,15 @@ public extension Api { public static func parse_messageActionSetMessagesTTL(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt64() } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSetMessagesTTL(period: _1!) + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionSetMessagesTTL(flags: _1!, period: _2!, autoSettingFrom: _3) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index ec423f45e7..b84c84469b 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -2,6 +2,7 @@ public extension Api.auth { enum CodeType: TypeConstructorDescription { case codeTypeCall case codeTypeFlashCall + case codeTypeFragmentSms case codeTypeMissedCall case codeTypeSms @@ -18,6 +19,12 @@ public extension Api.auth { buffer.appendInt32(577556219) } + break + case .codeTypeFragmentSms: + if boxed { + buffer.appendInt32(116234636) + } + break case .codeTypeMissedCall: if boxed { @@ -40,6 +47,8 @@ public extension Api.auth { return ("codeTypeCall", []) case .codeTypeFlashCall: return ("codeTypeFlashCall", []) + case .codeTypeFragmentSms: + return ("codeTypeFragmentSms", []) case .codeTypeMissedCall: return ("codeTypeMissedCall", []) case .codeTypeSms: @@ -53,6 +62,9 @@ public extension Api.auth { public static func parse_codeTypeFlashCall(_ reader: BufferReader) -> CodeType? { return Api.auth.CodeType.codeTypeFlashCall } + public static func parse_codeTypeFragmentSms(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeFragmentSms + } public static func parse_codeTypeMissedCall(_ reader: BufferReader) -> CodeType? { return Api.auth.CodeType.codeTypeMissedCall } @@ -326,6 +338,7 @@ public extension Api.auth { case sentCodeTypeCall(length: Int32) case sentCodeTypeEmailCode(flags: Int32, emailPattern: String, length: Int32, nextPhoneLoginDate: Int32?) case sentCodeTypeFlashCall(pattern: String) + case sentCodeTypeFragmentSms(url: String, length: Int32) case sentCodeTypeMissedCall(prefix: String, length: Int32) case sentCodeTypeSetUpEmailRequired(flags: Int32) case sentCodeTypeSms(length: Int32) @@ -359,6 +372,13 @@ public extension Api.auth { } serializeString(pattern, buffer: buffer, boxed: false) break + case .sentCodeTypeFragmentSms(let url, let length): + if boxed { + buffer.appendInt32(-648651719) + } + serializeString(url, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break case .sentCodeTypeMissedCall(let prefix, let length): if boxed { buffer.appendInt32(-2113903484) @@ -391,6 +411,8 @@ public extension Api.auth { return ("sentCodeTypeEmailCode", [("flags", String(describing: flags)), ("emailPattern", String(describing: emailPattern)), ("length", String(describing: length)), ("nextPhoneLoginDate", String(describing: nextPhoneLoginDate))]) case .sentCodeTypeFlashCall(let pattern): return ("sentCodeTypeFlashCall", [("pattern", String(describing: pattern))]) + case .sentCodeTypeFragmentSms(let url, let length): + return ("sentCodeTypeFragmentSms", [("url", String(describing: url)), ("length", String(describing: length))]) case .sentCodeTypeMissedCall(let prefix, let length): return ("sentCodeTypeMissedCall", [("prefix", String(describing: prefix)), ("length", String(describing: length))]) case .sentCodeTypeSetUpEmailRequired(let flags): @@ -453,6 +475,20 @@ public extension Api.auth { return nil } } + public static func parse_sentCodeTypeFragmentSms(_ reader: BufferReader) -> SentCodeType? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.auth.SentCodeType.sentCodeTypeFragmentSms(url: _1!, length: _2!) + } + else { + return nil + } + } public static func parse_sentCodeTypeMissedCall(_ reader: BufferReader) -> SentCodeType? { var _1: String? _1 = parseString(reader) @@ -1134,89 +1170,3 @@ public extension Api.contacts { } } -public extension Api.contacts { - enum TopPeers: TypeConstructorDescription { - case topPeers(categories: [Api.TopPeerCategoryPeers], chats: [Api.Chat], users: [Api.User]) - case topPeersDisabled - case topPeersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .topPeers(let categories, let chats, let users): - if boxed { - buffer.appendInt32(1891070632) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(categories.count)) - for item in categories { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .topPeersDisabled: - if boxed { - buffer.appendInt32(-1255369827) - } - - break - case .topPeersNotModified: - if boxed { - buffer.appendInt32(-567906571) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .topPeers(let categories, let chats, let users): - return ("topPeers", [("categories", String(describing: categories)), ("chats", String(describing: chats)), ("users", String(describing: users))]) - case .topPeersDisabled: - return ("topPeersDisabled", []) - case .topPeersNotModified: - return ("topPeersNotModified", []) - } - } - - public static func parse_topPeers(_ reader: BufferReader) -> TopPeers? { - var _1: [Api.TopPeerCategoryPeers]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeerCategoryPeers.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.TopPeers.topPeers(categories: _1!, chats: _2!, users: _3!) - } - else { - return nil - } - } - public static func parse_topPeersDisabled(_ reader: BufferReader) -> TopPeers? { - return Api.contacts.TopPeers.topPeersDisabled - } - public static func parse_topPeersNotModified(_ reader: BufferReader) -> TopPeers? { - return Api.contacts.TopPeers.topPeersNotModified - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index f3e8113f14..fa5f9162c9 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -1,3 +1,89 @@ +public extension Api.contacts { + enum TopPeers: TypeConstructorDescription { + case topPeers(categories: [Api.TopPeerCategoryPeers], chats: [Api.Chat], users: [Api.User]) + case topPeersDisabled + case topPeersNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .topPeers(let categories, let chats, let users): + if boxed { + buffer.appendInt32(1891070632) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(categories.count)) + for item in categories { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .topPeersDisabled: + if boxed { + buffer.appendInt32(-1255369827) + } + + break + case .topPeersNotModified: + if boxed { + buffer.appendInt32(-567906571) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .topPeers(let categories, let chats, let users): + return ("topPeers", [("categories", String(describing: categories)), ("chats", String(describing: chats)), ("users", String(describing: users))]) + case .topPeersDisabled: + return ("topPeersDisabled", []) + case .topPeersNotModified: + return ("topPeersNotModified", []) + } + } + + public static func parse_topPeers(_ reader: BufferReader) -> TopPeers? { + var _1: [Api.TopPeerCategoryPeers]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeerCategoryPeers.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.contacts.TopPeers.topPeers(categories: _1!, chats: _2!, users: _3!) + } + else { + return nil + } + } + public static func parse_topPeersDisabled(_ reader: BufferReader) -> TopPeers? { + return Api.contacts.TopPeers.topPeersDisabled + } + public static func parse_topPeersNotModified(_ reader: BufferReader) -> TopPeers? { + return Api.contacts.TopPeers.topPeersNotModified + } + + } +} public extension Api.help { enum AppUpdate: TypeConstructorDescription { case appUpdate(flags: Int32, id: Int32, version: String, text: String, entities: [Api.MessageEntity], document: Api.Document?, url: String?, sticker: Api.Document?) @@ -1220,125 +1306,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum BotCallbackAnswer: TypeConstructorDescription { - case botCallbackAnswer(flags: Int32, message: String?, url: String?, cacheTime: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botCallbackAnswer(let flags, let message, let url, let cacheTime): - if boxed { - buffer.appendInt32(911761060) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - serializeInt32(cacheTime, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .botCallbackAnswer(let flags, let message, let url, let cacheTime): - return ("botCallbackAnswer", [("flags", String(describing: flags)), ("message", String(describing: message)), ("url", String(describing: url)), ("cacheTime", String(describing: cacheTime))]) - } - } - - public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: String? - if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum BotResults: TypeConstructorDescription { - case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users): - if boxed { - buffer.appendInt32(-1803769784) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {switchPm!.serialize(buffer, true)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results.count)) - for item in results { - item.serialize(buffer, true) - } - serializeInt32(cacheTime, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users): - return ("botResults", [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("nextOffset", String(describing: nextOffset)), ("switchPm", String(describing: switchPm)), ("results", String(describing: results)), ("cacheTime", String(describing: cacheTime)), ("users", String(describing: users))]) - } - } - - public static func parse_botResults(_ reader: BufferReader) -> BotResults? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: Api.InlineBotSwitchPM? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM - } } - var _5: [Api.BotInlineResult]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self) - } - var _6: Int32? - _6 = reader.readInt32() - var _7: [Api.User]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, results: _5!, cacheTime: _6!, users: _7!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index c478d9c39a..2be1f04d09 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -1,3 +1,125 @@ +public extension Api.messages { + enum BotCallbackAnswer: TypeConstructorDescription { + case botCallbackAnswer(flags: Int32, message: String?, url: String?, cacheTime: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botCallbackAnswer(let flags, let message, let url, let cacheTime): + if boxed { + buffer.appendInt32(911761060) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + serializeInt32(cacheTime, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .botCallbackAnswer(let flags, let message, let url, let cacheTime): + return ("botCallbackAnswer", [("flags", String(describing: flags)), ("message", String(describing: message)), ("url", String(describing: url)), ("cacheTime", String(describing: cacheTime))]) + } + } + + public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: String? + if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum BotResults: TypeConstructorDescription { + case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users): + if boxed { + buffer.appendInt32(-1803769784) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {switchPm!.serialize(buffer, true)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(results.count)) + for item in results { + item.serialize(buffer, true) + } + serializeInt32(cacheTime, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users): + return ("botResults", [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("nextOffset", String(describing: nextOffset)), ("switchPm", String(describing: switchPm)), ("results", String(describing: results)), ("cacheTime", String(describing: cacheTime)), ("users", String(describing: users))]) + } + } + + public static func parse_botResults(_ reader: BufferReader) -> BotResults? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } + var _4: Api.InlineBotSwitchPM? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM + } } + var _5: [Api.BotInlineResult]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self) + } + var _6: Int32? + _6 = reader.readInt32() + var _7: [Api.User]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, results: _5!, cacheTime: _6!, users: _7!) + } + else { + return nil + } + } + + } +} public extension Api.messages { enum ChatAdminsWithInvites: TypeConstructorDescription { case chatAdminsWithInvites(admins: [Api.ChatAdminWithInvites], users: [Api.User]) diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index f5443acdc0..5ca1f4e3b3 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -2853,6 +2853,21 @@ public extension Api.functions.contacts { }) } } +public extension Api.functions.contacts { + static func exportContactToken() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-127582169) + + return (FunctionDescription(name: "contacts.exportContactToken", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedContactToken? in + let reader = BufferReader(buffer) + var result: Api.ExportedContactToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedContactToken + } + return result + }) + } +} public extension Api.functions.contacts { static func getBlocked(offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2964,6 +2979,21 @@ public extension Api.functions.contacts { }) } } +public extension Api.functions.contacts { + static func importContactToken(token: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(318789512) + serializeString(token, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.importContactToken", parameters: [("token", String(describing: token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} public extension Api.functions.contacts { static func importContacts(contacts: [Api.InputContact]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index 052c017a21..9b7ed73b7a 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -874,14 +874,14 @@ public extension Api { } public extension Api { enum Dialog: TypeConstructorDescription { - case dialog(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?) + case dialog(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?, ttlPeriod: Int32?) case dialogFolder(flags: Int32, folder: Api.Folder, peer: Api.Peer, topMessage: Int32, unreadMutedPeersCount: Int32, unreadUnmutedPeersCount: Int32, unreadMutedMessagesCount: Int32, unreadUnmutedMessagesCount: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .dialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let notifySettings, let pts, let draft, let folderId): + case .dialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let notifySettings, let pts, let draft, let folderId, let ttlPeriod): if boxed { - buffer.appendInt32(-1460809483) + buffer.appendInt32(-712374074) } serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) @@ -895,6 +895,7 @@ public extension Api { if Int(flags) & Int(1 << 0) != 0 {serializeInt32(pts!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {draft!.serialize(buffer, true)} if Int(flags) & Int(1 << 4) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} break case .dialogFolder(let flags, let folder, let peer, let topMessage, let unreadMutedPeersCount, let unreadUnmutedPeersCount, let unreadMutedMessagesCount, let unreadUnmutedMessagesCount): if boxed { @@ -914,8 +915,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .dialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let notifySettings, let pts, let draft, let folderId): - return ("dialog", [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount)), ("unreadMentionsCount", String(describing: unreadMentionsCount)), ("unreadReactionsCount", String(describing: unreadReactionsCount)), ("notifySettings", String(describing: notifySettings)), ("pts", String(describing: pts)), ("draft", String(describing: draft)), ("folderId", String(describing: folderId))]) + case .dialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let notifySettings, let pts, let draft, let folderId, let ttlPeriod): + return ("dialog", [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount)), ("unreadMentionsCount", String(describing: unreadMentionsCount)), ("unreadReactionsCount", String(describing: unreadReactionsCount)), ("notifySettings", String(describing: notifySettings)), ("pts", String(describing: pts)), ("draft", String(describing: draft)), ("folderId", String(describing: folderId)), ("ttlPeriod", String(describing: ttlPeriod))]) case .dialogFolder(let flags, let folder, let peer, let topMessage, let unreadMutedPeersCount, let unreadUnmutedPeersCount, let unreadMutedMessagesCount, let unreadUnmutedMessagesCount): return ("dialogFolder", [("flags", String(describing: flags)), ("folder", String(describing: folder)), ("peer", String(describing: peer)), ("topMessage", String(describing: topMessage)), ("unreadMutedPeersCount", String(describing: unreadMutedPeersCount)), ("unreadUnmutedPeersCount", String(describing: unreadUnmutedPeersCount)), ("unreadMutedMessagesCount", String(describing: unreadMutedMessagesCount)), ("unreadUnmutedMessagesCount", String(describing: unreadUnmutedMessagesCount))]) } @@ -952,6 +953,8 @@ public extension Api { } } var _12: Int32? if Int(_1!) & Int(1 << 4) != 0 {_12 = reader.readInt32() } + var _13: Int32? + if Int(_1!) & Int(1 << 5) != 0 {_13 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -964,8 +967,9 @@ public extension Api { let _c10 = (Int(_1!) & Int(1 << 0) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 1) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.Dialog.dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, unreadReactionsCount: _8!, notifySettings: _9!, pts: _10, draft: _11, folderId: _12) + let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { + return Api.Dialog.dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, unreadReactionsCount: _8!, notifySettings: _9!, pts: _10, draft: _11, folderId: _12, ttlPeriod: _13) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index d6cc8b29fe..42ddc4ef5a 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -916,6 +916,46 @@ public extension Api { } } +public extension Api { + enum ExportedContactToken: TypeConstructorDescription { + case exportedContactToken(url: String, expires: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedContactToken(let url, let expires): + if boxed { + buffer.appendInt32(1103040667) + } + serializeString(url, buffer: buffer, boxed: false) + serializeInt32(expires, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedContactToken(let url, let expires): + return ("exportedContactToken", [("url", String(describing: url)), ("expires", String(describing: expires))]) + } + } + + public static func parse_exportedContactToken(_ reader: BufferReader) -> ExportedContactToken? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.ExportedContactToken.exportedContactToken(url: _1!, expires: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum ExportedMessageLink: TypeConstructorDescription { case exportedMessageLink(link: String, html: String) @@ -1092,121 +1132,3 @@ public extension Api { } } -public extension Api { - enum ForumTopic: TypeConstructorDescription { - case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, fromId: Api.Peer, notifySettings: Api.PeerNotifySettings, draft: Api.DraftMessage?) - case forumTopicDeleted(id: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .forumTopic(let flags, let id, let date, let title, let iconColor, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let fromId, let notifySettings, let draft): - if boxed { - buffer.appendInt32(1903173033) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeInt32(iconColor, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} - serializeInt32(topMessage, buffer: buffer, boxed: false) - serializeInt32(readInboxMaxId, buffer: buffer, boxed: false) - serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false) - serializeInt32(unreadCount, buffer: buffer, boxed: false) - serializeInt32(unreadMentionsCount, buffer: buffer, boxed: false) - serializeInt32(unreadReactionsCount, buffer: buffer, boxed: false) - fromId.serialize(buffer, true) - notifySettings.serialize(buffer, true) - if Int(flags) & Int(1 << 4) != 0 {draft!.serialize(buffer, true)} - break - case .forumTopicDeleted(let id): - if boxed { - buffer.appendInt32(37687451) - } - serializeInt32(id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .forumTopic(let flags, let id, let date, let title, let iconColor, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let fromId, let notifySettings, let draft): - return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount)), ("unreadMentionsCount", String(describing: unreadMentionsCount)), ("unreadReactionsCount", String(describing: unreadReactionsCount)), ("fromId", String(describing: fromId)), ("notifySettings", String(describing: notifySettings)), ("draft", String(describing: draft))]) - case .forumTopicDeleted(let id): - return ("forumTopicDeleted", [("id", String(describing: id))]) - } - } - - public static func parse_forumTopic(_ reader: BufferReader) -> ForumTopic? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() } - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: Int32? - _9 = reader.readInt32() - var _10: Int32? - _10 = reader.readInt32() - var _11: Int32? - _11 = reader.readInt32() - var _12: Int32? - _12 = reader.readInt32() - var _13: Api.Peer? - if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _14: Api.PeerNotifySettings? - if let signature = reader.readInt32() { - _14 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings - } - var _15: Api.DraftMessage? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.DraftMessage - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = (Int(_1!) & Int(1 << 4) == 0) || _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconColor: _5!, iconEmojiId: _6, topMessage: _7!, readInboxMaxId: _8!, readOutboxMaxId: _9!, unreadCount: _10!, unreadMentionsCount: _11!, unreadReactionsCount: _12!, fromId: _13!, notifySettings: _14!, draft: _15) - } - else { - return nil - } - } - public static func parse_forumTopicDeleted(_ reader: BufferReader) -> ForumTopic? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.ForumTopic.forumTopicDeleted(id: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index 7177c5d984..f59d75fea7 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -1,3 +1,121 @@ +public extension Api { + enum ForumTopic: TypeConstructorDescription { + case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, fromId: Api.Peer, notifySettings: Api.PeerNotifySettings, draft: Api.DraftMessage?) + case forumTopicDeleted(id: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .forumTopic(let flags, let id, let date, let title, let iconColor, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let fromId, let notifySettings, let draft): + if boxed { + buffer.appendInt32(1903173033) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeInt32(iconColor, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} + serializeInt32(topMessage, buffer: buffer, boxed: false) + serializeInt32(readInboxMaxId, buffer: buffer, boxed: false) + serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false) + serializeInt32(unreadCount, buffer: buffer, boxed: false) + serializeInt32(unreadMentionsCount, buffer: buffer, boxed: false) + serializeInt32(unreadReactionsCount, buffer: buffer, boxed: false) + fromId.serialize(buffer, true) + notifySettings.serialize(buffer, true) + if Int(flags) & Int(1 << 4) != 0 {draft!.serialize(buffer, true)} + break + case .forumTopicDeleted(let id): + if boxed { + buffer.appendInt32(37687451) + } + serializeInt32(id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .forumTopic(let flags, let id, let date, let title, let iconColor, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let fromId, let notifySettings, let draft): + return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount)), ("unreadMentionsCount", String(describing: unreadMentionsCount)), ("unreadReactionsCount", String(describing: unreadReactionsCount)), ("fromId", String(describing: fromId)), ("notifySettings", String(describing: notifySettings)), ("draft", String(describing: draft))]) + case .forumTopicDeleted(let id): + return ("forumTopicDeleted", [("id", String(describing: id))]) + } + } + + public static func parse_forumTopic(_ reader: BufferReader) -> ForumTopic? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() } + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Int32? + _9 = reader.readInt32() + var _10: Int32? + _10 = reader.readInt32() + var _11: Int32? + _11 = reader.readInt32() + var _12: Int32? + _12 = reader.readInt32() + var _13: Api.Peer? + if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _14: Api.PeerNotifySettings? + if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + } + var _15: Api.DraftMessage? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.DraftMessage + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = (Int(_1!) & Int(1 << 4) == 0) || _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconColor: _5!, iconEmojiId: _6, topMessage: _7!, readInboxMaxId: _8!, readOutboxMaxId: _9!, unreadCount: _10!, unreadMentionsCount: _11!, unreadReactionsCount: _12!, fromId: _13!, notifySettings: _14!, draft: _15) + } + else { + return nil + } + } + public static func parse_forumTopicDeleted(_ reader: BufferReader) -> ForumTopic? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.ForumTopic.forumTopicDeleted(id: _1!) + } + else { + return nil + } + } + + } +} public extension Api { enum Game: TypeConstructorDescription { case game(flags: Int32, id: Int64, accessHash: Int64, shortName: String, title: String, description: String, photo: Api.Photo, document: Api.Document?) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 7d23b54086..1bccf451f4 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -72,8 +72,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) })) } - case let .messageActionSetMessagesTTL(period): - return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period)) + case let .messageActionSetMessagesTTL(_, period, autoSettingFrom): + return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: period, autoSettingSource: autoSettingFrom.flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) })) case let .messageActionGroupCallScheduled(call, scheduleDate): switch call { case let .inputGroupCall(id, accessHash): diff --git a/submodules/TelegramCore/Sources/SecretChats/SetSecretChatMessageAutoremoveTimeoutInteractively.swift b/submodules/TelegramCore/Sources/SecretChats/SetSecretChatMessageAutoremoveTimeoutInteractively.swift index 2172531520..2ee9e38f96 100644 --- a/submodules/TelegramCore/Sources/SecretChats/SetSecretChatMessageAutoremoveTimeoutInteractively.swift +++ b/submodules/TelegramCore/Sources/SecretChats/SetSecretChatMessageAutoremoveTimeoutInteractively.swift @@ -4,19 +4,23 @@ import SwiftSignalKit func _internal_setSecretChatMessageAutoremoveTimeoutInteractively(account: Account, peerId: PeerId, timeout: Int32?) -> Signal { return account.postbox.transaction { transaction -> Void in - if let peer = transaction.getPeer(peerId) as? TelegramSecretChat, let state = transaction.getPeerChatState(peerId) as? SecretChatState { - if state.messageAutoremoveTimeout != timeout { - let updatedPeer = peer.withUpdatedMessageAutoremoveTimeout(timeout) - let updatedState = state.withUpdatedMessageAutoremoveTimeout(timeout) - if !updatedPeer.isEqual(peer) { - updatePeers(transaction: transaction, peers: [updatedPeer], update: { $1 }) - } - if updatedState != state { - transaction.setPeerChatState(peerId, state: updatedState) - } - - let _ = enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: [(true, .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaAction(action: TelegramMediaActionType.messageAutoremoveTimeoutUpdated(timeout == nil ? 0 : timeout!))), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))]) + _internal_setSecretChatMessageAutoremoveTimeoutInteractively(transaction: transaction, account: account, peerId: peerId, timeout: timeout) + } +} + +func _internal_setSecretChatMessageAutoremoveTimeoutInteractively(transaction: Transaction, account: Account, peerId: PeerId, timeout: Int32?) { + if let peer = transaction.getPeer(peerId) as? TelegramSecretChat, let state = transaction.getPeerChatState(peerId) as? SecretChatState { + if state.messageAutoremoveTimeout != timeout { + let updatedPeer = peer.withUpdatedMessageAutoremoveTimeout(timeout) + let updatedState = state.withUpdatedMessageAutoremoveTimeout(timeout) + if !updatedPeer.isEqual(peer) { + updatePeers(transaction: transaction, peers: [updatedPeer], update: { $1 }) } + if updatedState != state { + transaction.setPeerChatState(peerId, state: updatedState) + } + + let _ = enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: [(true, .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaAction(action: TelegramMediaActionType.messageAutoremoveTimeoutUpdated(period: timeout == nil ? 0 : timeout!, autoSettingSource: nil))), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))]) } } } diff --git a/submodules/TelegramCore/Sources/State/AccountState.swift b/submodules/TelegramCore/Sources/State/AccountState.swift index 6a5ef5f6cc..6391924183 100644 --- a/submodules/TelegramCore/Sources/State/AccountState.swift +++ b/submodules/TelegramCore/Sources/State/AccountState.swift @@ -34,6 +34,8 @@ extension SentAuthorizationCodeType { self = .email(emailPattern: emailPattern, length: length, nextPhoneLoginDate: nextPhoneLoginDate, appleSignInAllowed: (flags & (1 << 0)) != 0, setup: false) case let .sentCodeTypeSetUpEmailRequired(flags): self = .emailSetupRequired(appleSignInAllowed: (flags & (1 << 0)) != 0) + case let .sentCodeTypeFragmentSms(_, length): + self = .sms(length: length) } } } @@ -49,6 +51,8 @@ extension AuthorizationCodeNextType { self = .flashCall case .codeTypeMissedCall: self = .missedCall + case .codeTypeFragmentSms: + self = .sms } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index ec67ed0068..3c4519bfd1 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -2211,7 +2211,7 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable for dialog in dialogs { switch dialog { - case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId): + case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId, ttlPeriod): let peerId = peer.peerId updatedState.setNeedsHoleFromPreviousState(peerId: peerId, namespace: Namespaces.Message.Cloud, validateChannelPts: pts) @@ -2251,6 +2251,8 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable if !isExcludedFromChatList { updatedState.updatePeerChatInclusion(peerId: peerId, groupId: PeerGroupId(rawValue: folderId ?? 0), changedGroup: false) } + + updatedState.updateAutoremoveTimeout(peer: peer, value: ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:))) let notificationSettings = TelegramPeerNotificationSettings(apiSettings: notifySettings) updatedState.updateNotificationSettings(.peer(peerId: peer.peerId, threadId: nil), notificationSettings: notificationSettings) @@ -2452,8 +2454,9 @@ private func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Net let apiNotificationSettings: Api.PeerNotifySettings let apiMarkedUnread: Bool let groupId: PeerGroupId + let apiTtlPeriod: Int32? switch dialog { - case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, folderId): + case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, folderId, ttlPeriod): apiPeer = peer apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId @@ -2465,6 +2468,7 @@ private func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Net apiNotificationSettings = peerNotificationSettings apiChannelPts = pts groupId = PeerGroupId(rawValue: folderId ?? 0) + apiTtlPeriod = ttlPeriod case .dialogFolder: assertionFailure() continue loop @@ -2491,6 +2495,8 @@ private func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Net updatedState.updatePeerChatInclusion(peerId: peerId, groupId: groupId, changedGroup: false) + updatedState.updateAutoremoveTimeout(peer: apiPeer, value: apiTtlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:))) + resetForumTopics.insert(peerId) } @@ -2747,12 +2753,12 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo apiTimeout = timeout - var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32)? + var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, ttlPeriod: Int32?)? switch dialog { - case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, _, pts, _, _): + case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, _, pts, _, _, ttlPeriod): if let pts = pts { - parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) + parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, ttlPeriod) } case .dialogFolder: break @@ -2760,10 +2766,12 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo var resetForumTopics = Set() - if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) = parameters { + if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, ttlPeriod) = parameters { updatedState.updateChannelState(peer.peerId, pts: pts) updatedState.updateChannelInvalidationPts(peer.peerId, invalidationPts: pts) + updatedState.updateAutoremoveTimeout(peer: peer, value: ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:))) + updatedState.mergeChats(chats) updatedState.mergeUsers(users) diff --git a/submodules/TelegramCore/Sources/State/FetchChatList.swift b/submodules/TelegramCore/Sources/State/FetchChatList.swift index 2784a77955..e6dc6c7981 100644 --- a/submodules/TelegramCore/Sources/State/FetchChatList.swift +++ b/submodules/TelegramCore/Sources/State/FetchChatList.swift @@ -22,6 +22,7 @@ struct ParsedDialogs { let channelStates: [PeerId: Int32] let topMessageIds: [PeerId: MessageId] let storeMessages: [StoreMessage] + let ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] let lowerNonPinnedIndex: MessageIndex? let referencedFolders: [PeerGroupId: PeerGroupUnreadCountersSummary] @@ -53,6 +54,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] var channelStates: [PeerId: Int32] = [:] var topMessageIds: [PeerId: MessageId] = [:] + var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:] var storeMessages: [StoreMessage] = [] var nonPinnedDialogsTopMessageIds = Set() @@ -85,7 +87,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], var apiChannelPts: Int32? let apiNotificationSettings: Api.PeerNotifySettings switch dialog { - case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, _): + case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, _, ttlPeriod): if let peer = peers[peer.peerId] { var isExluded = false if let group = peer as? TelegramGroup { @@ -107,6 +109,9 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], apiUnreadReactionsCount = unreadReactionsCount apiNotificationSettings = peerNotificationSettings apiChannelPts = pts + + ttlPeriods[peer.peerId] = .known(ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:))) + let isPinned = (flags & (1 << 2)) != 0 if !isPinned { nonPinnedDialogsTopMessageIds.insert(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: topMessage)) @@ -180,6 +185,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], channelStates: channelStates, topMessageIds: topMessageIds, storeMessages: storeMessages, + ttlPeriods: ttlPeriods, lowerNonPinnedIndex: lowerNonPinnedIndex, referencedFolders: referencedFolders @@ -191,6 +197,7 @@ struct FetchedChatList { var peers: [Peer] var peerPresences: [PeerId: Api.User] var notificationSettings: [PeerId: PeerNotificationSettings] + var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] @@ -298,6 +305,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo var peers: [Peer] = [] var peerPresences: [PeerId: Api.User] = [:] var notificationSettings: [PeerId: PeerNotificationSettings] = [:] + var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:] var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] @@ -308,6 +316,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo peers.append(contentsOf: parsedRemoteChats.peers) peerPresences.merge(parsedRemoteChats.peerPresences, uniquingKeysWith: { _, updated in updated }) notificationSettings.merge(parsedRemoteChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) + ttlPeriods.merge(parsedRemoteChats.ttlPeriods, uniquingKeysWith: { _, updated in updated }) readStates.merge(parsedRemoteChats.readStates, uniquingKeysWith: { _, updated in updated }) mentionTagSummaries.merge(parsedRemoteChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) reactionTagSummaries.merge(parsedRemoteChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated }) @@ -319,6 +328,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo peers.append(contentsOf: parsedPinnedChats.peers) peerPresences.merge(parsedPinnedChats.peerPresences, uniquingKeysWith: { _, updated in updated }) notificationSettings.merge(parsedPinnedChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) + ttlPeriods.merge(parsedPinnedChats.ttlPeriods, uniquingKeysWith: { _, updated in updated }) readStates.merge(parsedPinnedChats.readStates, uniquingKeysWith: { _, updated in updated }) mentionTagSummaries.merge(parsedPinnedChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) reactionTagSummaries.merge(parsedPinnedChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated }) @@ -342,6 +352,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo peers.append(contentsOf: folderChats.peers) peerPresences.merge(folderChats.peerPresences, uniquingKeysWith: { _, updated in updated }) notificationSettings.merge(folderChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) + ttlPeriods.merge(folderChats.ttlPeriods, uniquingKeysWith: { _, updated in updated }) readStates.merge(folderChats.readStates, uniquingKeysWith: { _, updated in updated }) mentionTagSummaries.merge(folderChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) reactionTagSummaries.merge(folderChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated }) @@ -376,6 +387,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo peers: peers, peerPresences: peerPresences, notificationSettings: notificationSettings, + ttlPeriods: ttlPeriods, readStates: readStates, mentionTagSummaries: mentionTagSummaries, reactionTagSummaries: reactionTagSummaries, diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 9a05d2475e..cfe19705ef 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -815,6 +815,20 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId let _ = transaction.addMessages(additionalMessages, location: .Random) transaction.resetIncomingReadStates(fetchedChats.readStates) + for (peerId, autoremoveValue) in fetchedChats.ttlPeriods { + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else if let current = current as? CachedGroupData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else if let current = current as? CachedChannelData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else { + return current + } + }) + } + transaction.replaceChatListHole(groupId: groupId, index: hole.index, hole: fetchedChats.lowerNonPinnedIndex.flatMap(ChatListHole.init)) for peerId in fetchedChats.chatPeerIds { diff --git a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift index 6a274e41e1..7acb49ea2a 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift @@ -367,7 +367,7 @@ private func synchronizeUnseenPersonalMentionsTag(postbox: Postbox, network: Net let apiTopMessage: Int32 let apiUnreadMentionsCount: Int32 switch dialog { - case let .dialog(_, _, topMessage, _, _, _, unreadMentionsCount, _, _, _, _, _): + case let .dialog(_, _, topMessage, _, _, _, unreadMentionsCount, _, _, _, _, _, _): apiTopMessage = topMessage apiUnreadMentionsCount = unreadMentionsCount @@ -409,7 +409,7 @@ private func synchronizeUnseenReactionsTag(postbox: Postbox, network: Network, e let apiTopMessage: Int32 let apiUnreadReactionsCount: Int32 switch dialog { - case let .dialog(_, _, topMessage, _, _, _, _, unreadReactionsCount, _, _, _, _): + case let .dialog(_, _, topMessage, _, _, _, _, unreadReactionsCount, _, _, _, _, _): apiTopMessage = topMessage apiUnreadReactionsCount = unreadReactionsCount diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift index cb0f03c24e..686dde8074 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift @@ -132,6 +132,7 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox, var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] var channelStates: [PeerId: Int32] = [:] var notificationSettings: [PeerId: PeerNotificationSettings] = [:] + var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:] var remoteItemIds: [PinnedItemId] = [] @@ -159,9 +160,10 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox, let apiUnreadCount: Int32 let apiMarkedUnread: Bool var apiChannelPts: Int32? + let apiTtlPeriod: Int32? let apiNotificationSettings: Api.PeerNotifySettings switch dialog { - case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, peerNotificationSettings, pts, _, _): + case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, peerNotificationSettings, pts, _, _, ttlPeriod): apiPeer = peer apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId @@ -170,6 +172,7 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox, apiMarkedUnread = (flags & (1 << 3)) != 0 apiNotificationSettings = peerNotificationSettings apiChannelPts = pts + apiTtlPeriod = ttlPeriod case .dialogFolder: //assertionFailure() continue loop @@ -189,6 +192,8 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox, } notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) + + ttlPeriods[peerId] = .known(apiTtlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:))) } for message in messages { @@ -220,6 +225,20 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox, transaction.updateCurrentPeerNotificationSettings(notificationSettings) + for (peerId, autoremoveValue) in ttlPeriods { + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else if let current = current as? CachedGroupData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else if let current = current as? CachedChannelData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else { + return current + } + }) + } + var allPeersWithMessages = Set() for message in storeMessages { if !allPeersWithMessages.contains(message.id.peerId) { diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 64576b119a..b48949a095 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -950,7 +950,7 @@ public final class PendingMessageManager { var sentAsAction = false for media in message.media { if let media = media as? TelegramMediaAction { - if case let .messageAutoremoveTimeoutUpdated(value) = media.action { + if case let .messageAutoremoveTimeoutUpdated(value, _) = media.action { sentAsAction = true let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: message.id.peerId, operation: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, timeout: value, messageId: message.id), state: state) if updatedState != state { diff --git a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift index 62bbdbbd6e..686b2ee897 100644 --- a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift @@ -581,7 +581,7 @@ extension StoreMessage { case .decryptedMessageActionScreenshotMessages: self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]) } } } @@ -908,7 +908,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionScreenshotMessages: return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1140,7 +1140,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionScreenshotMessages: return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1419,7 +1419,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionScreenshotMessages: return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1620,7 +1620,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionScreenshotMessages: return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: diff --git a/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift b/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift index f6d1487941..bed0706667 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift @@ -87,7 +87,7 @@ private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) let apiMarkedUnread: Bool var apiChannelPts: Int32 = 0 switch dialog { - case let .dialog(flags, _, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, _, pts, _, _): + case let .dialog(flags, _, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, _, pts, _, _, _): apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId apiReadOutboxMaxId = readOutboxMaxId diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index f5f4064547..0bf994384c 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -181,7 +181,7 @@ extension Api.Peer { extension Api.Dialog { var peerId: PeerId? { switch self { - case let .dialog(_, peer, _, _, _, _, _, _, _, _, _, _): + case let .dialog(_, peer, _, _, _, _, _, _, _, _, _, _, _): return peer.peerId case .dialogFolder: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index 5f4885c97c..e050fbcbcd 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -133,6 +133,22 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { return nil } return Message.AssociatedThreadInfo(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor) + }, + decodeAutoremoveTimeout: { cachedData in + if let cachedData = cachedData as? CachedUserData { + if case let .known(value) = cachedData.autoremoveTimeout { + return value?.effectiveValue + } + } else if let cachedData = cachedData as? CachedGroupData { + if case let .known(value) = cachedData.autoremoveTimeout { + return value?.effectiveValue + } + } else if let cachedData = cachedData as? CachedChannelData { + if case let .known(value) = cachedData.autoremoveTimeout { + return value?.effectiveValue + } + } + return nil } ) }() diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index b247aeb851..66203a036c 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -80,7 +80,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case groupMigratedToChannel(channelId: PeerId) case historyCleared case historyScreenshot - case messageAutoremoveTimeoutUpdated(Int32) + case messageAutoremoveTimeoutUpdated(period: Int32, autoSettingSource: PeerId?) case gameScore(gameId: Int64, score: Int32) case phoneCall(callId: Int64, discardReason: PhoneCallDiscardReason?, duration: Int32?, isVideo: Bool) case paymentSent(currency: String, totalAmount: Int64, invoiceSlug: String?, isRecurringInit: Bool, isRecurringUsed: Bool) @@ -125,7 +125,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case 11: self = .historyScreenshot case 12: - self = .messageAutoremoveTimeoutUpdated(decoder.decodeInt32ForKey("t", orElse: 0)) + self = .messageAutoremoveTimeoutUpdated(period: decoder.decodeInt32ForKey("t", orElse: 0), autoSettingSource: decoder.decodeOptionalInt64ForKey("src").flatMap(PeerId.init)) case 13: self = .gameScore(gameId: decoder.decodeInt64ForKey("i", orElse: 0), score: decoder.decodeInt32ForKey("s", orElse: 0)) case 14: @@ -218,9 +218,14 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeInt32(10, forKey: "_rawValue") case .historyScreenshot: encoder.encodeInt32(11, forKey: "_rawValue") - case let .messageAutoremoveTimeoutUpdated(timeout): + case let .messageAutoremoveTimeoutUpdated(timeout, autoSettingSource): encoder.encodeInt32(12, forKey: "_rawValue") encoder.encodeInt32(timeout, forKey: "t") + if let autoSettingSource = autoSettingSource { + encoder.encodeInt64(autoSettingSource.toInt64(), forKey: "src") + } else { + encoder.encodeNil(forKey: "src") + } case let .gameScore(gameId, score): encoder.encodeInt32(13, forKey: "_rawValue") encoder.encodeInt64(gameId, forKey: "i") diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index 39a269b597..96d56034ed 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -126,6 +126,7 @@ public final class EngineChatList: Equatable { public let topForumTopicItems: [EngineChatList.ForumTopicData] public let hasFailed: Bool public let isContact: Bool + public let autoremoveTimeout: Int32? public init( id: Id, @@ -142,7 +143,8 @@ public final class EngineChatList: Equatable { forumTopicData: ForumTopicData?, topForumTopicItems: [EngineChatList.ForumTopicData], hasFailed: Bool, - isContact: Bool + isContact: Bool, + autoremoveTimeout: Int32? ) { self.id = id self.index = index @@ -159,6 +161,7 @@ public final class EngineChatList: Equatable { self.topForumTopicItems = topForumTopicItems self.hasFailed = hasFailed self.isContact = isContact + self.autoremoveTimeout = autoremoveTimeout } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -207,6 +210,9 @@ public final class EngineChatList: Equatable { if lhs.isContact != rhs.isContact { return false } + if lhs.autoremoveTimeout != rhs.autoremoveTimeout { + return false + } return true } } @@ -408,7 +414,7 @@ public extension EngineChatList.RelativePosition { extension EngineChatList.Item { convenience init?(_ entry: ChatListEntry) { switch entry { - case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailed, isContact): + case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailed, isContact, autoremoveTimeout): var draft: EngineChatList.Draft? if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp { if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { @@ -470,7 +476,8 @@ extension EngineChatList.Item { forumTopicData: forumTopicDataValue, topForumTopicItems: topForumTopicItems, hasFailed: hasFailed, - isContact: isContact + isContact: isContact, + autoremoveTimeout: autoremoveTimeout ) case .HoleEntry: return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index e22f2c225e..0f9804ca34 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -635,6 +635,7 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox, var peers: [Peer] = [] var peerPresences: [PeerId: Api.User] = [:] var notificationSettings: [PeerId: PeerNotificationSettings] = [:] + var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:] var channelStates: [PeerId: Int32] = [:] switch result { @@ -654,7 +655,7 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox, for dialog in dialogs { switch dialog { - case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId): + case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId, ttlPeriod): let peerId = peer.peerId if topMessage != 0 { @@ -708,6 +709,8 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox, notificationSettings[peer.peerId] = TelegramPeerNotificationSettings(apiSettings: notifySettings) + ttlPeriods[peer.peerId] = .known(ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:))) + transaction.resetIncomingReadStates([peerId: [Namespaces.Message.Cloud: .idBased(maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: false)]]) transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, maxId: topMessage) @@ -754,6 +757,20 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox, updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) transaction.updateCurrentPeerNotificationSettings(notificationSettings) + + for (peerId, autoremoveValue) in ttlPeriods { + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else if let current = current as? CachedGroupData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else if let current = current as? CachedChannelData { + return current.withUpdatedAutoremoveTimeout(autoremoveValue) + } else { + return current + } + }) + } } |> ignoreValues } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 3a56c20cbe..f82bef8fe0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -154,6 +154,56 @@ public extension TelegramEngine { return _internal_setChatMessageAutoremoveTimeoutInteractively(account: self.account, peerId: peerId, timeout: timeout) } } + + public func setChatMessageAutoremoveTimeouts(peerIds: [EnginePeer.Id], timeout: Int32?) -> Signal { + return self.account.postbox.transaction { transaction -> Void in + for peerId in peerIds { + if peerId.namespace == Namespaces.Peer.SecretChat { + _internal_setSecretChatMessageAutoremoveTimeoutInteractively(transaction: transaction, account: self.account, peerId: peerId, timeout: timeout) + } else { + var canManage = false + guard let peer = transaction.getPeer(peerId) else { + continue + } + if let user = peer as? TelegramUser { + if user.botInfo == nil { + canManage = true + } + } else if let _ = peer as? TelegramSecretChat { + canManage = true + } else if let group = peer as? TelegramGroup { + canManage = !group.hasBannedPermission(.banChangeInfo) + } else if let channel = peer as? TelegramChannel { + canManage = channel.hasPermission(.changeInfo) + } + + if !canManage { + continue + } + + let cachedData = transaction.getPeerCachedData(peerId: peerId) + var currentValue: Int32? + if let cachedData = cachedData as? CachedUserData { + if case let .known(value) = cachedData.autoremoveTimeout { + currentValue = value?.effectiveValue + } + } else if let cachedData = cachedData as? CachedGroupData { + if case let .known(value) = cachedData.autoremoveTimeout { + currentValue = value?.effectiveValue + } + } else if let cachedData = cachedData as? CachedChannelData { + if case let .known(value) = cachedData.autoremoveTimeout { + currentValue = value?.effectiveValue + } + } + if currentValue != timeout { + let _ = _internal_setChatMessageAutoremoveTimeoutInteractively(account: self.account, peerId: peerId, timeout: timeout).start() + } + } + } + } + |> ignoreValues + } public func updateChannelSlowModeInteractively(peerId: PeerId, timeout: Int32?) -> Signal { return _internal_updateChannelSlowModeInteractively(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, timeout: timeout) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index d585251543..747e48d983 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -38,6 +38,7 @@ public enum PresentationResourceKey: Int32 { case itemListDownArrow case itemListDisclosureArrow + case disclosureOptionArrowsImage case itemListDisclosureLocked case itemListCheckIcon case itemListSecondaryCheckIcon @@ -331,4 +332,6 @@ public enum PresentationResourceParameterKey: Hashable { case chatEntityKeyboardLock(color: UInt32) case chatInputMediaPanelGridDismissImage(color: UInt32) + + case statusAutoremoveIcon(isActive: Bool) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift index b4e4fa2c95..c6235cba73 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift @@ -408,4 +408,10 @@ public struct PresentationResourcesChatList { return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor) }) } + + public static func statusAutoremoveIcon(_ theme: PresentationTheme, isActive: Bool) -> UIImage? { + return theme.image(PresentationResourceParameterKey.statusAutoremoveIcon(isActive: isActive), { theme in + return generateTintedImage(image: UIImage(bundleImageName: isActive ? "Chat List/StatusIconAutoremoveOn" : "Chat List/StatusIconAutoremoveOff"), color: isActive ? theme.list.itemAccentColor : theme.list.itemSecondaryTextColor) + }) + } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 9a3204ee0b..9a1a21b42b 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -33,6 +33,12 @@ public struct PresentationResourcesItemList { }) } + public static func disclosureOptionArrowsImage(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.disclosureOptionArrowsImage.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Item List/ContextDisclosureArrow"), color: theme.list.disclosureArrowColor) + }) + } + public static func disclosureLockedImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListDisclosureLocked.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: theme.list.disclosureArrowColor) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index d741df560c..7c2e6a37a8 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -336,7 +336,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } case .channelMigratedFromGroup, .groupMigratedToChannel: attributedString = NSAttributedString(string: "", font: titleFont, textColor: primaryTextColor) - case let .messageAutoremoveTimeoutUpdated(timeout): + case let .messageAutoremoveTimeoutUpdated(timeout, _): let authorString: String if let author = messageMainPeer(message) { authorString = author.compactDisplayTitle diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOff.imageset/AutoremoveIconOff.svg b/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOff.imageset/AutoremoveIconOff.svg new file mode 100644 index 0000000000..2a5f77534c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOff.imageset/AutoremoveIconOff.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOff.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOff.imageset/Contents.json new file mode 100644 index 0000000000..cc433a9fa8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOff.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "AutoremoveIconOff.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOn.imageset/AutoremoveIconOn.svg b/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOn.imageset/AutoremoveIconOn.svg new file mode 100644 index 0000000000..2356320a54 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOn.imageset/AutoremoveIconOn.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOn.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOn.imageset/Contents.json new file mode 100644 index 0000000000..d45c4c3e03 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOn.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "AutoremoveIconOn.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Item List/ContextDisclosureArrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/ContextDisclosureArrow.imageset/Contents.json new file mode 100644 index 0000000000..0d3e2ddcd6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/ContextDisclosureArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ContextDisclosureArrow.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Item List/ContextDisclosureArrow.imageset/ContextDisclosureArrow.svg b/submodules/TelegramUI/Images.xcassets/Item List/ContextDisclosureArrow.imageset/ContextDisclosureArrow.svg new file mode 100644 index 0000000000..0d19d01392 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/ContextDisclosureArrow.imageset/ContextDisclosureArrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/Contents.json new file mode 100644 index 0000000000..e1a2f2326c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "TimerIcon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/TimerIcon.svg b/submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/TimerIcon.svg new file mode 100644 index 0000000000..f570cdb55a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/TimerIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/submodules/TelegramUI/Resources/Animations/GlobalAutoRemove.tgs b/submodules/TelegramUI/Resources/Animations/GlobalAutoRemove.tgs new file mode 100644 index 0000000000..14c228a7f3 Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/GlobalAutoRemove.tgs differ diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 3293308241..f6043f4ef8 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -786,19 +786,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let _ = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat { canSetupAutoremoveTimeout = false } else if let group = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup { - if case .creator = group.role { + if !group.hasBannedPermission(.banChangeInfo) { canSetupAutoremoveTimeout = true - } else if case let .admin(rights, _) = group.role { - if rights.rights.contains(.canDeleteMessages) { - canSetupAutoremoveTimeout = true - } } } else if let user = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramUser { if user.id != strongSelf.context.account.peerId && user.botInfo == nil { canSetupAutoremoveTimeout = true } } else if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel { - if channel.hasPermission(.deleteAllMessages) { + if channel.hasPermission(.changeInfo) { canSetupAutoremoveTimeout = true } } @@ -8511,19 +8507,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout canSetupAutoremoveTimeout = true } else if let group = peer as? TelegramGroup { - if case .creator = group.role { + if !group.hasBannedPermission(.banChangeInfo) { canSetupAutoremoveTimeout = true - } else if case let .admin(rights, _) = group.role { - if rights.rights.contains(.canDeleteMessages) { - canSetupAutoremoveTimeout = true - } } } else if let user = peer as? TelegramUser { if user.id != strongSelf.context.account.peerId && user.botInfo == nil { canSetupAutoremoveTimeout = true } } else if let channel = peer as? TelegramChannel { - if channel.hasPermission(.deleteAllMessages) { + if channel.hasPermission(.changeInfo) { canSetupAutoremoveTimeout = true } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 3ee921b2e4..7f748a8b56 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -295,19 +295,15 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte canSetupAutoremoveTimeout = true } } else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup { - if case .creator = group.role { + if !group.hasBannedPermission(.banChangeInfo) { canSetupAutoremoveTimeout = true - } else if case let .admin(rights, _) = group.role { - if rights.rights.contains(.canDeleteMessages) { - canSetupAutoremoveTimeout = true - } } } else if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser { if user.botInfo == nil { canSetupAutoremoveTimeout = true } } else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel { - if channel.hasPermission(.deleteAllMessages) { + if channel.hasPermission(.changeInfo) { canSetupAutoremoveTimeout = true } } diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index ee9e1f9815..d8f862acaf 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -28,6 +28,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection private let params: ContactMultiselectionControllerParams private let context: AccountContext private let mode: ContactMultiselectionControllerMode + private let isPeerEnabled: ((EnginePeer) -> Bool)? private let titleView: CounterContollerTitleView @@ -83,6 +84,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection self.params = params self.context = params.context self.mode = params.mode + self.isPeerEnabled = params.isPeerEnabled self.options = params.options self.filters = params.filters self.limit = params.limit @@ -127,7 +129,9 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection }) switch self.mode { - case let .chatSelection(_, selectedChats, additionalCategories, _): + case let .chatSelection(chatSelection): + let selectedChats = chatSelection.selectedChats + let additionalCategories = chatSelection.additionalCategories let _ = (self.context.engine.data.get( EngineDataList( selectedChats.map(TelegramEngine.EngineData.Item.Peer.Peer.init) @@ -207,8 +211,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed)) self.navigationItem.rightBarButtonItem = self.rightNavigationButton rightNavigationButton.isEnabled = false - case let .chatSelection(title, _, _, _): - self.titleView.title = CounterContollerTitle(title: title, counter: "") + case let .chatSelection(chatSelection): + self.titleView.title = CounterContollerTitle(title: chatSelection.title, counter: "") let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed)) self.rightNavigationButton = rightNavigationButton self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed)) @@ -218,7 +222,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } override func loadDisplayNode() { - self.displayNode = ContactMultiselectionControllerNode(navigationBar: self.navigationBar, context: self.context, presentationData: self.presentationData, mode: self.mode, options: self.options, filters: self.filters, limit: self.limit, reachedSelectionLimit: self.params.reachedLimit) + self.displayNode = ContactMultiselectionControllerNode(navigationBar: self.navigationBar, context: self.context, presentationData: self.presentationData, mode: self.mode, isPeerEnabled: self.isPeerEnabled, options: self.options, filters: self.filters, limit: self.limit, reachedSelectionLimit: self.params.reachedLimit) switch self.contactsNode.contentNode { case let .contacts(contactsNode): self._listReady.set(contactsNode.ready) @@ -440,8 +444,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection break case let .chats(chatsNode): var categoryToken: EditableTokenListToken? - if case let .chatSelection(_, _, additionalCategories, _) = strongSelf.mode { - if let additionalCategories = additionalCategories { + if case let .chatSelection(chatSelection) = strongSelf.mode { + if let additionalCategories = chatSelection.additionalCategories { for i in 0 ..< additionalCategories.categories.count { if additionalCategories.categories[i].id == id { categoryToken = EditableTokenListToken(id: id, title: additionalCategories.categories[i].title, fixedPosition: i) diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index 50adb454d9..96b8579688 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -71,7 +71,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer - init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter], limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?) { + init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, options: [ContactListAdditionalOption], filters: [ContactListFilter], limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?) { self.navigationBar = navigationBar self.context = context @@ -94,9 +94,14 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { placeholder = self.presentationData.strings.Compose_TokenListPlaceholder } - if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode { - placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle - let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false) + if case let .chatSelection(chatSelection) = mode { + let placeholderValue = chatSelection.searchPlaceholder + let selectedChats = chatSelection.selectedChats + let additionalCategories = chatSelection.additionalCategories + let chatListFilters = chatSelection.chatListFilters + + placeholder = placeholderValue + let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters, displayAutoremoveTimeout: chatSelection.displayAutoremoveTimeout), isPeerEnabled: isPeerEnabled, theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false) if let limit = limit { chatListNode.selectionLimit = limit chatListNode.reachedSelectionLimit = reachedSelectionLimit diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 3511a2a2fb..578e332ed5 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -26,6 +26,9 @@ import MapResourceToAvatarSizes import ItemListAddressItem import ItemListVenueItem import LegacyMediaPickerUI +import ContextUI +import ChatTimerScreen +import AsyncDisplayKit private struct CreateGroupArguments { let context: AccountContext @@ -35,10 +38,12 @@ private struct CreateGroupArguments { let changeProfilePhoto: () -> Void let changeLocation: () -> Void let updateWithVenue: (TelegramMediaMap) -> Void + let updateAutoDelete: () -> Void } private enum CreateGroupSection: Int32 { case info + case autoDelete case members case location case venues @@ -46,17 +51,11 @@ private enum CreateGroupSection: Int32 { private enum CreateGroupEntryTag: ItemListItemTag { case info + case autoDelete func isEqual(to other: ItemListItemTag) -> Bool { if let other = other as? CreateGroupEntryTag { - switch self { - case .info: - if case .info = other { - return true - } else { - return false - } - } + return self == other } else { return false } @@ -66,6 +65,8 @@ private enum CreateGroupEntryTag: ItemListItemTag { private enum CreateGroupEntry: ItemListNodeEntry { case groupInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?) case setProfilePhoto(PresentationTheme, String) + case autoDelete(title: String, value: String) + case autoDeleteInfo(String) case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, PeerPresence?) case locationHeader(PresentationTheme, String) case location(PresentationTheme, PeerGeoLocation) @@ -78,6 +79,8 @@ private enum CreateGroupEntry: ItemListNodeEntry { switch self { case .groupInfo, .setProfilePhoto: return CreateGroupSection.info.rawValue + case .autoDelete, .autoDeleteInfo: + return CreateGroupSection.autoDelete.rawValue case .member: return CreateGroupSection.members.rawValue case .locationHeader, .location, .changeLocation, .locationInfo: @@ -93,8 +96,12 @@ private enum CreateGroupEntry: ItemListNodeEntry { return 0 case .setProfilePhoto: return 1 + case .autoDelete: + return 2 + case .autoDeleteInfo: + return 3 case let .member(index, _, _, _, _, _, _): - return 2 + index + return 4 + index case .locationHeader: return 10000 case .location: @@ -146,6 +153,18 @@ private enum CreateGroupEntry: ItemListNodeEntry { } else { return false } + case let .autoDelete(title, value): + if case .autoDelete(title, value) = rhs { + return true + } else { + return false + } + case let .autoDeleteInfo(text): + if case .autoDeleteInfo(text) = rhs { + return true + } else { + return false + } case let .member(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsPeer, lhsPresence): if case let .member(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsPeer, rhsPresence) = rhs { if lhsIndex != rhsIndex { @@ -244,6 +263,12 @@ private enum CreateGroupEntry: ItemListNodeEntry { return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { arguments.changeProfilePhoto() }) + case let .autoDelete(text, value): + return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { + arguments.updateAutoDelete() + }, tag: CreateGroupEntryTag.autoDelete) + case let .autoDeleteInfo(text): + return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .member(_, _, _, dateTimeFormat, nameDisplayOrder, peer, presence): return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: presence.flatMap(EnginePeer.Presence.init), text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) case let .locationHeader(_, title): @@ -273,25 +298,7 @@ private struct CreateGroupState: Equatable { var nameSetFromVenue: Bool var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar? var location: PeerGeoLocation? - - static func ==(lhs: CreateGroupState, rhs: CreateGroupState) -> Bool { - if lhs.creating != rhs.creating { - return false - } - if lhs.editingName != rhs.editingName { - return false - } - if lhs.nameSetFromVenue != rhs.nameSetFromVenue { - return false - } - if lhs.avatar != rhs.avatar { - return false - } - if lhs.location != rhs.location { - return false - } - return true - } + var autoremoveTimeout: Int32 } private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView, venues: [TelegramMediaMap]?) -> [CreateGroupEntry] { @@ -303,6 +310,16 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat entries.append(.groupInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, groupInfoState, state.avatar)) + //TODO:localize + let autoRemoveText: String + if state.autoremoveTimeout == 0 { + autoRemoveText = "Off" + } else { + autoRemoveText = timeIntervalString(strings: presentationData.strings, value: state.autoremoveTimeout) + } + entries.append(.autoDelete(title: "Auto-Delete Messages", value: autoRemoveText)) + entries.append(.autoDeleteInfo("Automatically delete messages sent in this group for everyone after a period of time.")) + var peers: [Peer] = [] for peerId in peerIds { if let peer = view.peers[peerId] { @@ -365,7 +382,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] location = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address ?? "") } - let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), nameSetFromVenue: false, avatar: nil, location: location) + let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), nameSetFromVenue: false, avatar: nil, location: location, autoremoveTimeout: 0) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in @@ -375,9 +392,11 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] var replaceControllerImpl: ((ViewController) -> Void)? var dismissImpl: (() -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? + var presentInGlobalOverlay: ((ViewController) -> Void)? var pushImpl: ((ViewController) -> Void)? var endEditingImpl: (() -> Void)? var ensureItemVisibleImpl: ((CreateGroupEntryTag, Bool) -> Void)? + var findAutoremoveReferenceNode: (() -> ItemListDisclosureItemNode?)? let actionsDisposable = DisposableSet() @@ -422,29 +441,13 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] } endEditingImpl?() + let autoremoveTimeout = stateValue.with({ $0 }).autoremoveTimeout + let ttlPeriod: Int32? = autoremoveTimeout == 0 ? nil : autoremoveTimeout + let createSignal: Signal switch mode { case .generic: - if title.contains("*forum") { - createSignal = context.engine.peers.createSupergroup(title: title, description: nil) - |> map(Optional.init) - |> mapError { error -> CreateGroupError in - switch error { - case .generic: - return .generic - case .restricted: - return .restricted - case .tooMuchJoined: - return .tooMuchJoined - case .tooMuchLocationBasedGroups: - return .tooMuchLocationBasedGroups - case let .serverProvided(error): - return .serverProvided(error) - } - } - } else { - createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: nil) - } + createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: ttlPeriod) case .supergroup: createSignal = context.engine.peers.createSupergroup(title: title, description: nil) |> map(Optional.init) @@ -821,6 +824,86 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] } }) ensureItemVisibleImpl?(.info, true) + }, updateAutoDelete: { + var subItems: [ContextMenuItem] = [] + + let currentValue = stateValue.with({ $0 }).autoremoveTimeout + + let applyValue: (Int32) -> Void = { value in + updateState { state in + var state = state + state.autoremoveTimeout = value + return state + } + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + //TODO:localize + subItems.append(.action(ContextMenuActionItem(text: "Off", icon: { theme in + if currentValue == 0 { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return nil + } + }, action: { _, f in + applyValue(0) + f(.default) + }))) + subItems.append(.separator) + + var presetValues: [Int32] = [ + 1 * 24 * 60 * 60, + 7 * 24 * 60 * 60, + 31 * 24 * 60 * 60 + ] + if currentValue != 0 && !presetValues.contains(currentValue) { + presetValues.append(currentValue) + presetValues.sort() + } + + for value in presetValues { + subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: presentationData.strings, value: value), icon: { theme in + if currentValue == value { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return nil + } + }, action: { _, f in + applyValue(value) + f(.default) + }))) + } + + //TODO:localize + subItems.append(.action(ContextMenuActionItem(text: "Set Custom Time...", icon: { _ in + return nil + }, action: { _, f in + f(.default) + + let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: currentValue == 0 ? nil : currentValue, dismissByTapOutside: true, completion: { value in + applyValue(value) + }) + endEditingImpl?() + presentControllerImpl?(controller, nil) + }))) + + if let sourceNode = findAutoremoveReferenceNode?() { + let items: Signal = .single(ContextController.Items(content: .list(subItems))) + let source: ContextContentSource = .reference(CreateGroupContextReferenceContentSource(sourceView: sourceNode.labelNode.view)) + + let contextController = ContextController( + account: context.account, + presentationData: presentationData, + source: source, + items: items, + gesture: nil + ) + sourceNode.updateHasContextMenu(hasContextMenu: true) + contextController.dismissed = { [weak sourceNode] in + sourceNode?.updateHasContextMenu(hasContextMenu: false) + } + presentInGlobalOverlay?(contextController) + } }) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get()), .single(nil) |> then(venuesPromise.get())) @@ -856,6 +939,9 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] presentControllerImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) } + presentInGlobalOverlay = { [weak controller] c in + controller?.presentInGlobalOverlay(c, with: nil) + } pushImpl = { [weak controller] c in controller?.push(c) } @@ -888,5 +974,41 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] } }) } + + findAutoremoveReferenceNode = { [weak controller] in + guard let controller else { + return nil + } + + let targetTag: CreateGroupEntryTag = .autoDelete + var resultItemNode: ItemListItemNode? + controller.forEachItemNode { itemNode in + if let itemNode = itemNode as? ItemListItemNode { + if let tag = itemNode.tag, tag.isEqual(to: targetTag) { + resultItemNode = itemNode + return + } + } + } + + if let resultItemNode = resultItemNode as? ItemListDisclosureItemNode { + return resultItemNode + } else { + return nil + } + } + return controller } + +private final class CreateGroupContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0)) + } +} diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index e20fc4de48..0c4dacf69f 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -459,47 +459,55 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur case let .settings(section): dismissInput() switch section { - case .theme: - if let navigationController = navigationController { - let controller = themeSettingsController(context: context) + case .theme: + if let navigationController = navigationController { + let controller = themeSettingsController(context: context) + controller.navigationPresentation = .modal + + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is ThemeSettingsController) } + controllers.append(controller) + + navigationController.setViewControllers(controllers, animated: true) + } + case .devices: + if let navigationController = navigationController { + let activeSessions = deferred { () -> Signal<(ActiveSessionsContext, Int, WebSessionsContext), NoError> in + let activeSessionsContext = context.engine.privacy.activeSessions() + let webSessionsContext = context.engine.privacy.webSessions() + let otherSessionCount = activeSessionsContext.state + |> map { state -> Int in + return state.sessions.filter({ !$0.isCurrent }).count + } + |> distinctUntilChanged + + return otherSessionCount + |> map { value in + return (activeSessionsContext, value, webSessionsContext) + } + } + + let _ = (activeSessions + |> take(1) + |> deliverOnMainQueue).start(next: { activeSessionsContext, count, webSessionsContext in + let controller = recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false) controller.navigationPresentation = .modal var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is ThemeSettingsController) } + controllers = controllers.filter { !($0 is RecentSessionsController) } controllers.append(controller) navigationController.setViewControllers(controllers, animated: true) - } - case .devices: - if let navigationController = navigationController { - let activeSessions = deferred { () -> Signal<(ActiveSessionsContext, Int, WebSessionsContext), NoError> in - let activeSessionsContext = context.engine.privacy.activeSessions() - let webSessionsContext = context.engine.privacy.webSessions() - let otherSessionCount = activeSessionsContext.state - |> map { state -> Int in - return state.sessions.filter({ !$0.isCurrent }).count - } - |> distinctUntilChanged - - return otherSessionCount - |> map { value in - return (activeSessionsContext, value, webSessionsContext) - } - } - - let _ = (activeSessions - |> take(1) - |> deliverOnMainQueue).start(next: { activeSessionsContext, count, webSessionsContext in - let controller = recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false) - controller.navigationPresentation = .modal - - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is RecentSessionsController) } - controllers.append(controller) - - navigationController.setViewControllers(controllers, animated: true) - }) - } + }) + } + case .autoremoveMessages: + let _ = (context.engine.privacy.requestAccountPrivacySettings() + |> take(1) + |> deliverOnMainQueue).start(next: { settings in + navigationController?.pushViewController(globalAutoremoveScreen(context: context, initialValue: settings.messageAutoremoveTimeout ?? 0, updated: { _ in }), animated: true) + }) + case .twoStepAuth: + break } case let .premiumOffer(reference): dismissInput() diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index a6da62508f..40805cc794 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -810,12 +810,14 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if let path = parsedUrl.pathComponents.last { var section: ResolvedUrlSettingsSection? switch path { - case "themes": - section = .theme - case "devices": - section = .devices - default: - break + case "themes": + section = .theme + case "devices": + section = .devices + case "password": + section = .twoStepAuth + default: + break } if let section = section { handleResolvedUrl(.settings(section)) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 483ecb0448..f8059cec1f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4430,19 +4430,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout canSetupAutoremoveTimeout = false } else if let group = chatPeer as? TelegramGroup { - if case .creator = group.role { + if !group.hasBannedPermission(.banChangeInfo) { canSetupAutoremoveTimeout = true - } else if case let .admin(rights, _) = group.role { - if rights.rights.contains(.canDeleteMessages) { - canSetupAutoremoveTimeout = true - } } } else if let user = chatPeer as? TelegramUser { if user.id != strongSelf.context.account.peerId && user.botInfo == nil { canSetupAutoremoveTimeout = true } } else if let channel = chatPeer as? TelegramChannel { - if channel.hasPermission(.deleteAllMessages) { + if channel.hasPermission(.changeInfo) { canSetupAutoremoveTimeout = true } } @@ -4654,8 +4650,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate subItems.append(.separator) - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in return nil + }, textLinkAction: { [weak c] in + c?.dismiss(completion: nil) + + guard let self else { + return + } + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + guard let self else { + return + } + self.controller?.view.endEditing(true) + }, contentContext: nil) }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) @@ -4798,9 +4806,27 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate subItems.append(.separator) - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in - return nil - }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + if case .group = channel.info { + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in + return nil + }, textLinkAction: { [weak c] in + c?.dismiss(completion: nil) + + guard let self else { + return + } + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + guard let self else { + return + } + self.controller?.view.endEditing(true) + }, contentContext: nil) + }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + } else { + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in + return nil + }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + } c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) @@ -4914,8 +4940,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate subItems.append(.separator) - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in return nil + }, textLinkAction: { [weak c] in + c?.dismiss(completion: nil) + + guard let self else { + return + } + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + guard let self else { + return + } + self.controller?.view.endEditing(true) + }, contentContext: nil) }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 2f51338a60..015e60954c 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -137,7 +137,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { chatListLocation = .chatList(groupId: .root) } - self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false) + self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil, displayAutoremoveTimeout: false), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false) super.init() diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index a4bdf14d58..f68c1a4960 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -751,6 +751,8 @@ public final class OngoingCallContext { private var signalingConnectionManager: QueueLocalObject? + private let audioDevice: SharedCallAudioDevice? + public static func versions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] { #if os(iOS) && DEBUG && false if "".isEmpty { @@ -780,6 +782,14 @@ public final class OngoingCallContext { self.logPath = logName.isEmpty ? "" : callLogsPath(account: self.account) + "/" + logName + ".log" let logPath = self.logPath + let audioDevice: SharedCallAudioDevice? + if !"".isEmpty { + audioDevice = SharedCallAudioDevice() + } else { + audioDevice = nil + } + self.audioDevice = audioDevice + let _ = try? FileManager.default.createDirectory(atPath: callLogsPath(account: account), withIntermediateDirectories: true, attributes: nil) self.tempStatsLogFile = EngineTempBox.shared.tempFile(fileName: "CallStats.json") @@ -900,7 +910,7 @@ public final class OngoingCallContext { callSessionManager.sendSignalingData(internalId: internalId, data: data) } } - }, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "", useManualAudioSessionControl: true) + }, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "", audioDevice: audioDevice) strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 6325fd73a7..b965c91dbd 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -11,6 +11,16 @@ #define UIView NSView #endif +@interface SharedCallAudioDevice : NSObject + +- (instancetype _Nonnull)init; + ++ (void)setupAudioSession; + +- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive; + +@end + @interface OngoingCallConnectionDescriptionWebrtc : NSObject @property (nonatomic, readonly) uint8_t reflectorId; @@ -223,7 +233,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId - useManualAudioSessionControl:(bool)useManualAudioSessionControl; + audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice; - (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 13dbe2c062..af82bcbf6d 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -43,6 +43,95 @@ #import "platform/darwin/TGRTCCVPixelBuffer.h" #include "rtc_base/logging.h" +namespace tgcalls { + +class SharedAudioDeviceModule { +public: + virtual ~SharedAudioDeviceModule() = default; + +public: + virtual rtc::scoped_refptr audioDeviceModule() = 0; +}; + +} + +class SharedAudioDeviceModuleImpl: public tgcalls::SharedAudioDeviceModule { +public: + SharedAudioDeviceModuleImpl() { + if (tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()) { + _audioDeviceModule = rtc::make_ref_counted(false, false, 1); + } else { + tgcalls::StaticThreads::getThreads()->getWorkerThread()->BlockingCall([&]() { + _audioDeviceModule = rtc::make_ref_counted(false, false, 1); + }); + } + } + + virtual ~SharedAudioDeviceModuleImpl() override { + if (tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()) { + _audioDeviceModule = nullptr; + } else { + tgcalls::StaticThreads::getThreads()->getWorkerThread()->BlockingCall([&]() { + _audioDeviceModule = nullptr; + }); + } + } + +public: + virtual rtc::scoped_refptr audioDeviceModule() override { + return _audioDeviceModule; + } + +private: + rtc::scoped_refptr _audioDeviceModule; +}; + +@implementation SharedCallAudioDevice { + std::shared_ptr> _audioDeviceModule; +} + +- (instancetype _Nonnull)init { + self = [super init]; + if (self != nil) { + _audioDeviceModule.reset(new tgcalls::ThreadLocalObject(tgcalls::StaticThreads::getThreads()->getWorkerThread(), []() mutable { + return (tgcalls::SharedAudioDeviceModule *)(new SharedAudioDeviceModuleImpl()); + })); + } + return self; +} + +- (void)dealloc { + _audioDeviceModule.reset(); +} + +- (std::shared_ptr>)getAudioDeviceModule { + return _audioDeviceModule; +} + ++ (void)setupAudioSession { + RTCAudioSessionConfiguration *sharedConfiguration = [RTCAudioSessionConfiguration webRTCConfiguration]; + sharedConfiguration.mode = AVAudioSessionModeVoiceChat; + sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers; + sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; + sharedConfiguration.outputNumberOfChannels = 1; + [RTCAudioSessionConfiguration setWebRTCConfiguration:sharedConfiguration]; + + [[RTCAudioSession sharedInstance] lockForConfiguration]; + [[RTCAudioSession sharedInstance] setConfiguration:sharedConfiguration active:false error:nil disableRecording:false]; + [[RTCAudioSession sharedInstance] unlockForConfiguration]; +} + +- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive { + if (isAudioSessionActive) { + [[RTCAudioSession sharedInstance] audioSessionDidActivate:[AVAudioSession sharedInstance]]; + } else { + [[RTCAudioSession sharedInstance] audioSessionDidDeactivate:[AVAudioSession sharedInstance]]; + } + [RTCAudioSession sharedInstance].isAudioEnabled = isAudioSessionActive; +} + +@end + @implementation OngoingCallConnectionDescriptionWebrtc - (instancetype _Nonnull)initWithReflectorId:(uint8_t)reflectorId hasStun:(bool)hasStun hasTurn:(bool)hasTurn hasTcp:(bool)hasTcp ip:(NSString * _Nonnull)ip port:(int32_t)port username:(NSString * _Nonnull)username password:(NSString * _Nonnull)password { @@ -709,6 +798,7 @@ tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls: int32_t _contextId; bool _useManualAudioSessionControl; + SharedCallAudioDevice *_audioDevice; OngoingCallNetworkTypeWebrtc _networkType; NSTimeInterval _callReceiveTimeout; @@ -876,7 +966,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId - useManualAudioSessionControl:(bool)useManualAudioSessionControl { + audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice { self = [super init]; if (self != nil) { _version = version; @@ -885,7 +975,9 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; assert([[OngoingCallThreadLocalContextWebrtc versionsWithIncludeReference:true] containsObject:version]); - _useManualAudioSessionControl = useManualAudioSessionControl; + _audioDevice = audioDevice; + + _useManualAudioSessionControl = true; [RTCAudioSession sharedInstance].useManualAudio = true; #ifdef WEBRTC_IOS @@ -992,6 +1084,11 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; [OngoingCallThreadLocalContextWebrtc ensureRegisteredImplementations]; + std::shared_ptr> audioDeviceModule; + if (_audioDevice) { + audioDeviceModule = [_audioDevice getAudioDeviceModule]; + } + __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; _tgVoip = tgcalls::Meta::Create([version UTF8String], (tgcalls::Descriptor){ .version = [version UTF8String], @@ -1116,8 +1213,12 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; } }]; }, - .createAudioDeviceModule = [](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr { - return rtc::make_ref_counted(false, false, 1); + .createAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr { + if (audioDeviceModule) { + return audioDeviceModule->getSyncAssumingSameThread()->audioDeviceModule(); + } else { + return rtc::make_ref_counted(false, false, 1); + } } }); _state = OngoingCallStateInitializing; diff --git a/submodules/WatchBridge/Sources/WatchBridge.swift b/submodules/WatchBridge/Sources/WatchBridge.swift index aa54f8d7f3..953ab45513 100644 --- a/submodules/WatchBridge/Sources/WatchBridge.swift +++ b/submodules/WatchBridge/Sources/WatchBridge.swift @@ -325,7 +325,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P } func makeBridgeChat(_ entry: ChatListEntry, strings: PresentationStrings) -> (TGBridgeChat, [Int64 : TGBridgeUser])? { - if case let .MessageEntry(index, messages, readState, _, _, renderedPeer, _, _, _, _, hasFailed, _) = entry { + if case let .MessageEntry(index, messages, readState, _, _, renderedPeer, _, _, _, _, hasFailed, _, _) = entry { guard index.messageIndex.id.peerId.namespace != Namespaces.Peer.SecretChat else { return nil }