diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 7552d56592..45d5181b52 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -88,6 +88,7 @@ swift_library( "//submodules/TelegramUI/Components/ChatListTitleView", "//submodules/AvatarNode:AvatarNode", "//submodules/AvatarVideoNode:AvatarVideoNode", + "//submodules/InviteLinksUI", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index e33cadd481..bf907a282f 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -14,6 +14,7 @@ import ItemListPeerActionItem import AvatarNode import ChatListFilterSettingsHeaderItem import PremiumUI +import InviteLinksUI private enum FilterSection: Int32, Hashable { case include @@ -32,6 +33,8 @@ private final class ChatListFilterPresetControllerArguments { let deleteExcludeCategory: (ChatListFilterExcludeCategory) -> Void let focusOnName: () -> Void let expandSection: (FilterSection) -> Void + let createLink: () -> Void + let openLink: (ExportedChatFolderLink) -> Void init( context: AccountContext, @@ -44,7 +47,9 @@ private final class ChatListFilterPresetControllerArguments { deleteIncludeCategory: @escaping (ChatListFilterIncludeCategory) -> Void, deleteExcludeCategory: @escaping (ChatListFilterExcludeCategory) -> Void, focusOnName: @escaping () -> Void, - expandSection: @escaping (FilterSection) -> Void + expandSection: @escaping (FilterSection) -> Void, + createLink: @escaping () -> Void, + openLink: @escaping (ExportedChatFolderLink) -> Void ) { self.context = context self.updateState = updateState @@ -57,6 +62,8 @@ private final class ChatListFilterPresetControllerArguments { self.deleteExcludeCategory = deleteExcludeCategory self.focusOnName = focusOnName self.expandSection = expandSection + self.createLink = createLink + self.openLink = openLink } } @@ -65,6 +72,7 @@ private enum ChatListFilterPresetControllerSection: Int32 { case name case includePeers case excludePeers + case inviteLinks } private enum ChatListFilterPresetEntryStableId: Hashable { @@ -76,6 +84,7 @@ private enum ChatListFilterPresetEntryStableId: Hashable { case excludeCategory(ChatListFilterExcludeCategory) case includeExpand case excludeExpand + case inviteLink(String) } private enum ChatListFilterPresetEntrySortId: Comparable { @@ -83,6 +92,9 @@ private enum ChatListFilterPresetEntrySortId: Comparable { case topIndex(Int) case includeIndex(Int) case excludeIndex(Int) + case bottomIndex(Int) + case inviteLink(Int) + case inviteLinkFooter static func <(lhs: ChatListFilterPresetEntrySortId, rhs: ChatListFilterPresetEntrySortId) -> Bool { switch lhs { @@ -103,6 +115,12 @@ private enum ChatListFilterPresetEntrySortId: Comparable { return true case .excludeIndex: return true + case .bottomIndex: + return true + case .inviteLink: + return true + case .inviteLinkFooter: + return true } case let .includeIndex(lhsIndex): switch rhs { @@ -114,6 +132,12 @@ private enum ChatListFilterPresetEntrySortId: Comparable { return lhsIndex < rhsIndex case .excludeIndex: return true + case .bottomIndex: + return true + case .inviteLink: + return true + case .inviteLinkFooter: + return true } case let .excludeIndex(lhsIndex): switch rhs { @@ -125,6 +149,63 @@ private enum ChatListFilterPresetEntrySortId: Comparable { return false case let .excludeIndex(rhsIndex): return lhsIndex < rhsIndex + case .bottomIndex: + return true + case .inviteLink: + return true + case .inviteLinkFooter: + return true + } + case let .bottomIndex(lhsIndex): + switch rhs { + case .screenHeader: + return false + case .topIndex: + return false + case .includeIndex: + return false + case .excludeIndex: + return false + case let .bottomIndex(rhsIndex): + return lhsIndex < rhsIndex + case .inviteLink: + return true + case .inviteLinkFooter: + return true + } + case let .inviteLink(lhsIndex): + switch rhs { + case .screenHeader: + return false + case .topIndex: + return false + case .includeIndex: + return false + case .excludeIndex: + return false + case .bottomIndex: + return false + case let .inviteLink(rhsIndex): + return lhsIndex < rhsIndex + case .inviteLinkFooter: + return false + } + case .inviteLinkFooter: + switch rhs { + case .screenHeader: + return false + case .topIndex: + return false + case .includeIndex: + return false + case .excludeIndex: + return false + case .bottomIndex: + return false + case .inviteLink: + return false + case .inviteLinkFooter: + return false } } } @@ -235,6 +316,10 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { case excludePeerInfo(String) case includeExpand(String) case excludeExpand(String) + case inviteLinkHeader + case inviteLinkCreate + case inviteLink(Int, ExportedChatFolderLink) + case inviteLinkInfo var section: ItemListSectionId { switch self { @@ -246,6 +331,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { return ChatListFilterPresetControllerSection.includePeers.rawValue case .excludePeersHeader, .addExcludePeer, .excludeCategory, .excludePeer, .excludePeerInfo, .excludeExpand: return ChatListFilterPresetControllerSection.excludePeers.rawValue + case .inviteLinkHeader, .inviteLinkCreate, .inviteLink, .inviteLinkInfo: + return ChatListFilterPresetControllerSection.inviteLinks.rawValue } } @@ -281,6 +368,14 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { return .peer(peer.peerId) case let .excludePeer(_, peer, _): return .peer(peer.peerId) + case .inviteLinkHeader: + return .index(11) + case .inviteLinkCreate: + return .index(12) + case let .inviteLink(_, link): + return .inviteLink(link.link) + case .inviteLinkInfo: + return .index(13) } } @@ -316,6 +411,14 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { return .excludeIndex(999) case .excludePeerInfo: return .excludeIndex(1000) + case .inviteLinkHeader: + return .bottomIndex(0) + case .inviteLinkCreate: + return .bottomIndex(1) + case let .inviteLink(index, _): + return .inviteLink(index) + case .inviteLinkInfo: + return .inviteLinkFooter } } @@ -413,6 +516,23 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: { arguments.expandSection(.exclude) }) + case .inviteLinkHeader: + //TODO:localize + return ItemListSectionHeaderItem(presentationData: presentationData, text: "INVITE LINK", sectionId: self.section) + case .inviteLinkCreate: + //TODO:localize + return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: "Share Folder with Others", sectionId: self.section, editing: false, action: { + arguments.createLink() + }) + case let .inviteLink(_, link): + return ItemListFolderInviteLinkListItem(presentationData: presentationData, invite: link, share: false, sectionId: self.section, style: .blocks) { invite in + arguments.openLink(invite) + } contextAction: { invite, node, gesture in + //arguments.linkContextAction(invite, canEdit, node, gesture) + } + case .inviteLinkInfo: + //TODO:localize + return ItemListTextItem(presentationData: presentationData, text: .markdown("Give vour friends and colleagues access to the entire folder including all of its groups and channels where you have the necessary rights."), sectionId: self.section) } } } @@ -455,7 +575,7 @@ private struct ChatListFilterPresetControllerState: Equatable { } } -private func chatListFilterPresetControllerEntries(presentationData: PresentationData, isNewFilter: Bool, state: ChatListFilterPresetControllerState, includePeers: [EngineRenderedPeer], excludePeers: [EngineRenderedPeer], isPremium: Bool, limit: Int32) -> [ChatListFilterPresetEntry] { +private func chatListFilterPresetControllerEntries(presentationData: PresentationData, isNewFilter: Bool, state: ChatListFilterPresetControllerState, includePeers: [EngineRenderedPeer], excludePeers: [EngineRenderedPeer], isPremium: Bool, limit: Int32, inviteLinks: [ExportedChatFolderLink]?) -> [ChatListFilterPresetEntry] { var entries: [ChatListFilterPresetEntry] = [] if isNewFilter { @@ -531,6 +651,19 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo)) + if !isNewFilter, let inviteLinks { + entries.append(.inviteLinkHeader) + entries.append(.inviteLinkCreate) + + var index = 0 + for link in inviteLinks { + entries.append(.inviteLink(index, link)) + index += 1 + } + + entries.append(.inviteLinkInfo) + } + return entries } @@ -887,7 +1020,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat let presentationData = context.sharedContext.currentPresentationData.with { $0 } var includePeers = ChatListFilterIncludePeers() includePeers.setPeers(state.additionallyIncludePeers) - let filter: ChatListFilter = .filter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) + let filter: ChatListFilter = .filter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(isShared: currentPreset?.data?.isShared ?? false, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) if let data = filter.data { switch chatListFilterType(data) { case .generic: @@ -924,6 +1057,12 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat var dismissImpl: (() -> Void)? var focusOnNameImpl: (() -> Void)? + let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil) + if let currentPreset { + sharedLinks.set(context.engine.peers.getExportedChatLinks(id: currentPreset.id) + |> map(Optional.init)) + } + let currentPeers = Atomic<[PeerId: EngineRenderedPeer]>(value: [:]) let stateWithPeers = statePromise.get() |> mapToSignal { state -> Signal<(ChatListFilterPresetControllerState, [EngineRenderedPeer], [EngineRenderedPeer]), NoError> in @@ -1025,7 +1164,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat let state = stateValue.with { $0 } var includePeers = ChatListFilterIncludePeers() includePeers.setPeers(state.additionallyIncludePeers) - let filter: ChatListFilter = .filter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) + let filter: ChatListFilter = .filter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(isShared: currentPreset?.data?.isShared ?? false, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) let _ = (context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { filters in @@ -1047,7 +1186,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat let state = stateValue.with { $0 } var includePeers = ChatListFilterIncludePeers() includePeers.setPeers(state.additionallyIncludePeers) - let filter: ChatListFilter = .filter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) + let filter: ChatListFilter = .filter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(isShared: currentPreset?.data?.isShared ?? false, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) let _ = (context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { filters in @@ -1124,6 +1263,24 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat state.expandedSections.insert(section) return state } + }, + createLink: { + if let currentPreset, let data = currentPreset.data, !data.includePeers.peers.isEmpty { + pushControllerImpl?(folderInviteLinkListController(context: context, filterId: currentPreset.id, allPeerIds: data.includePeers.peers, currentInvitation: nil)) + + /*if data.isShared { + + } else { + let _ = (context.engine.peers.exportChatFolder(filterId: currentPreset.id, title: "Link", peerIds: data.includePeers.peers) + |> deliverOnMainQueue).start(completed: { + dismissImpl?() + }) + }*/ + } + }, openLink: { link in + if let currentPreset, let data = currentPreset.data { + pushControllerImpl?(folderInviteLinkListController(context: context, filterId: currentPreset.id, allPeerIds: data.includePeers.peers, currentInvitation: link)) + } } ) @@ -1138,7 +1295,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat if currentPreset == nil { filterId = context.engine.peers.generateNewChatListFilterId(filters: filters) } - var updatedFilter: ChatListFilter = .filter(id: filterId, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) + var updatedFilter: ChatListFilter = .filter(id: filterId, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(isShared: currentPreset?.data?.isShared ?? false, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) var filters = filters if let _ = currentPreset { @@ -1182,10 +1339,11 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat context.account.postbox.peerView(id: context.account.peerId), context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) - ) + ), + sharedLinks.get() ) |> deliverOnMainQueue - |> map { presentationData, stateWithPeers, peerView, premiumLimits -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, stateWithPeers, peerView, premiumLimits, sharedLinks -> (ItemListControllerState, (ItemListNodeState, Any)) in let (state, includePeers, excludePeers) = stateWithPeers let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false @@ -1206,7 +1364,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(currentPreset != nil ? presentationData.strings.ChatListFolder_TitleEdit : presentationData.strings.ChatListFolder_TitleCreate), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetControllerEntries(presentationData: presentationData, isNewFilter: currentPreset == nil, state: state, includePeers: includePeers, excludePeers: excludePeers, isPremium: isPremium, limit: premiumLimits.maxFolderChatsCount), style: .blocks, emptyStateItem: nil, animateChanges: !skipStateAnimation) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetControllerEntries(presentationData: presentationData, isNewFilter: currentPreset == nil, state: state, includePeers: includePeers, excludePeers: excludePeers, isPremium: isPremium, limit: premiumLimits.maxFolderChatsCount, inviteLinks: sharedLinks), style: .blocks, emptyStateItem: nil, animateChanges: !skipStateAnimation) skipStateAnimation = false return (controllerState, (listState, arguments)) @@ -1261,7 +1419,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat var includePeers = ChatListFilterIncludePeers() includePeers.setPeers(state.additionallyIncludePeers) - let filter: ChatListFilter = .filter(id: currentPreset.id, title: state.name, emoticon: currentPreset.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) + let filter: ChatListFilter = .filter(id: currentPreset.id, title: state.name, emoticon: currentPreset.emoticon, data: ChatListFilterData(isShared: currentPreset.data?.isShared ?? false, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) if currentPresetWithoutPinnedPeers != filter { displaySaveAlert() return false diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 18087c7987..b1f36d3720 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -56,8 +56,8 @@ private enum ChatListFilterPresetListEntryStableId: Hashable { case suggestedPreset(ChatListFilterData) case suggestedAddCustom case listHeader - case preset(Int32) case addItem + case preset(Int32) case listFooter } @@ -75,7 +75,6 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { case suggestedPreset(index: PresetIndex, title: String, label: String, preset: ChatListFilterData) case suggestedAddCustom(String) case listHeader(String) - case addFolder case preset(index: PresetIndex, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool, isAllChats: Bool, isDisabled: Bool) case addItem(text: String, isEditing: Bool) case listFooter(String) @@ -86,7 +85,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { return ChatListFilterPresetListSection.screenHeader.rawValue case .suggestedListHeader, .suggestedPreset, .suggestedAddCustom: return ChatListFilterPresetListSection.suggested.rawValue - case .listHeader, .addFolder, .preset, .addItem, .listFooter: + case .listHeader, .preset, .addItem, .listFooter: return ChatListFilterPresetListSection.list.rawValue } } diff --git a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift new file mode 100644 index 0000000000..1136d97af8 --- /dev/null +++ b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift @@ -0,0 +1,471 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import PresentationDataUtils +import OverlayStatusController +import AccountContext +import AlertUI +import PresentationDataUtils +import AppBundle +import ContextUI +import TelegramStringFormatting +import ItemListPeerActionItem +import ItemListPeerItem +import ShareController +import UndoUI +import QrCodeUI + +private final class FolderInviteLinkListControllerArguments { + let context: AccountContext + let shareMainLink: (String) -> Void + let openMainLink: (String) -> Void + let copyLink: (String) -> Void + let mainLinkContextAction: (String?, ASDisplayNode, ContextGesture?) -> Void + let peerAction: (EnginePeer) -> Void + let generateLink: () -> Void + + init( + context: AccountContext, + shareMainLink: @escaping (String) -> Void, + openMainLink: @escaping (String) -> Void, + copyLink: @escaping (String) -> Void, + mainLinkContextAction: @escaping (String?, ASDisplayNode, ContextGesture?) -> Void, + peerAction: @escaping (EnginePeer) -> Void, + generateLink: @escaping () -> Void + ) { + self.context = context + self.shareMainLink = shareMainLink + self.openMainLink = openMainLink + self.copyLink = copyLink + self.mainLinkContextAction = mainLinkContextAction + self.peerAction = peerAction + self.generateLink = generateLink + } +} + +private enum InviteLinksListSection: Int32 { + case header + case mainLink + case peers +} + +private enum InviteLinksListEntry: ItemListNodeEntry { + enum StableId: Hashable { + case index(Int) + case peer(EnginePeer.Id) + } + + case header(String) + + case mainLinkHeader(String) + case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool) + + case peersHeader(String) + case peer(index: Int, peer: EnginePeer, isSelected: Bool) + case peersInfo(String) + + var section: ItemListSectionId { + switch self { + case .header: + return InviteLinksListSection.header.rawValue + case .mainLinkHeader, .mainLink: + return InviteLinksListSection.mainLink.rawValue + case .peersHeader, .peer, .peersInfo: + return InviteLinksListSection.peers.rawValue + } + } + + var stableId: StableId { + switch self { + case .header: + return .index(0) + case .mainLinkHeader: + return .index(1) + case .mainLink: + return .index(2) + case .peersHeader: + return .index(4) + case .peersInfo: + return .index(5) + case let .peer(_, peer, _): + return .peer(peer.id) + } + } + + var sortIndex: Int { + switch self { + case .header: + return 0 + case .mainLinkHeader: + return 1 + case .mainLink: + return 2 + case .peersHeader: + return 4 + case let .peer(index, _, _): + return 10 + index + case .peersInfo: + return 1000 + } + } + + static func ==(lhs: InviteLinksListEntry, rhs: InviteLinksListEntry) -> Bool { + switch lhs { + case let .header(text): + if case .header(text) = rhs { + return true + } else { + return false + } + case let .mainLinkHeader(text): + if case .mainLinkHeader(text) = rhs { + return true + } else { + return false + } + case let .mainLink(lhsLink, lhsIsGenerating): + if case let .mainLink(rhsLink, rhsIsGenerating) = rhs, lhsLink == rhsLink, lhsIsGenerating == rhsIsGenerating { + return true + } else { + return false + } + case let .peersHeader(text): + if case .peersHeader(text) = rhs { + return true + } else { + return false + } + case let .peersInfo(text): + if case .peersInfo(text) = rhs { + return true + } else { + return false + } + case let .peer(index, peer, isSelected): + if case .peer(index, peer, isSelected) = rhs { + return true + } else { + return false + } + } + } + + static func <(lhs: InviteLinksListEntry, rhs: InviteLinksListEntry) -> Bool { + return lhs.sortIndex < rhs.sortIndex + } + + func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { + let arguments = arguments as! FolderInviteLinkListControllerArguments + switch self { + case let .header(text): + return InviteLinkHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animationName: "ChatListNewFolder", sectionId: self.section) + case let .mainLinkHeader(text): + return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) + case let .mainLink(link, isGenerating): + return ItemListFolderInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: link, count: 0, peers: [], displayButton: true, enableButton: !isGenerating, buttonTitle: link != nil ? "Share Invite Link" : "Generate Invite Link", displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { + if let link { + arguments.copyLink(link.link) + } + }, shareAction: { + if let link { + arguments.shareMainLink(link.link) + } else { + arguments.generateLink() + } + }, contextAction: { node, gesture in + arguments.mainLinkContextAction(link?.link, node, gesture) + }, viewAction: { + if let link { + arguments.openMainLink(link.link) + } + }) + case let .peersHeader(text): + return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) + case let .peersInfo(text): + return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) + case let .peer(_, peer, isSelected): + return ItemListPeerItem( + presentationData: presentationData, + dateTimeFormat: PresentationDateTimeFormat(), + nameDisplayOrder: presentationData.nameDisplayOrder, + context: arguments.context, + peer: peer, + presence: nil, + text: .none, + label: .none, + editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), + switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck), + enabled: true, + selectable: true, + sectionId: self.section, + action: { + arguments.peerAction(peer) + }, + setPeerIdWithRevealedOptions: { _, _ in + }, + removePeer: { _ in + } + ) + } + } +} + +private func folderInviteLinkListControllerEntries( + presentationData: PresentationData, + state: FolderInviteLinkListControllerState, + allPeers: [EnginePeer] +) -> [InviteLinksListEntry] { + var entries: [InviteLinksListEntry] = [] + + entries.append(.header("Anyone with this link can add Gaming Club folder and the 2 chats selected below.")) + + //TODO:localize + + entries.append(.mainLinkHeader("INVITE LINK")) + entries.append(.mainLink(link: state.currentLink, isGenerating: state.generatingLink)) + + entries.append(.peersHeader("\(allPeers.count) CHATS SELECTED")) + for peer in allPeers { + entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id))) + } + + return entries +} + +private struct FolderInviteLinkListControllerState: Equatable { + var currentLink: ExportedChatFolderLink? + var selectedPeerIds = Set() + var generatingLink: Bool = false +} + +public func folderInviteLinkListController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, filterId: Int32, allPeerIds: [PeerId], currentInvitation: ExportedChatFolderLink?) -> ViewController { + var pushControllerImpl: ((ViewController) -> Void)? + let _ = pushControllerImpl + var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + var presentInGlobalOverlayImpl: ((ViewController) -> Void)? + + var dismissTooltipsImpl: (() -> Void)? + + let actionsDisposable = DisposableSet() + + var initialState = FolderInviteLinkListControllerState() + initialState.currentLink = currentInvitation + for peerId in allPeerIds { + initialState.selectedPeerIds.insert(peerId) + } + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((FolderInviteLinkListControllerState) -> FolderInviteLinkListControllerState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + let _ = updateState + + let revokeLinkDisposable = MetaDisposable() + actionsDisposable.add(revokeLinkDisposable) + + let deleteAllRevokedLinksDisposable = MetaDisposable() + actionsDisposable.add(deleteAllRevokedLinksDisposable) + + var getControllerImpl: (() -> ViewController?)? + + let arguments = FolderInviteLinkListControllerArguments(context: context, shareMainLink: { inviteLink in + let shareController = ShareController(context: context, subject: .url(inviteLink), updatedPresentationData: updatedPresentationData) + shareController.completed = { peerIds in + let _ = (context.engine.data.get( + EngineDataList( + peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) + ) + ) + |> deliverOnMainQueue).start(next: { peerList in + let peers = peerList.compactMap { $0 } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let text: String + var savedMessages = false + if peerIds.count == 1, let peerId = peerIds.first, peerId == context.account.peerId { + text = presentationData.strings.InviteLink_InviteLinkForwardTooltip_SavedMessages_One + savedMessages = true + } else { + if peers.count == 1, let peer = peers.first { + let peerName = peer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.InviteLink_InviteLinkForwardTooltip_Chat_One(peerName).string + } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { + let firstPeerName = firstPeer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let secondPeerName = secondPeer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.InviteLink_InviteLinkForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string + } else if let peer = peers.first { + let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.InviteLink_InviteLinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string + } else { + text = "" + } + } + + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), nil) + }) + } + shareController.actionCompleted = { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + } + presentControllerImpl?(shareController, nil) + }, openMainLink: { _ in + }, copyLink: { link in + UIPasteboard.general.string = link + + dismissTooltipsImpl?() + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + }, mainLinkContextAction: { invite, node, gesture in + guard let node = node as? ContextReferenceContentNode, let controller = getControllerImpl?(), let invite = invite else { + return + } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + + dismissTooltipsImpl?() + + UIPasteboard.general.string = invite + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + }))) + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + + //presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil) + }))) + + let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + presentInGlobalOverlayImpl?(contextController) + }, peerAction: { peer in + updateState { state in + var state = state + + if state.selectedPeerIds.contains(peer.id) { + state.selectedPeerIds.remove(peer.id) + } else { + state.selectedPeerIds.insert(peer.id) + } + + return state + } + }, generateLink: { + let currentState = stateValue.with({ $0 }) + if !currentState.generatingLink { + updateState { state in + var state = state + + state.generatingLink = true + + return state + } + + actionsDisposable.add((context.engine.peers.exportChatFolder(filterId: filterId, title: "Link", peerIds: Array(currentState.selectedPeerIds)) + |> deliverOnMainQueue).start(next: { result in + updateState { state in + var state = state + + state.generatingLink = false + state.currentLink = result + + return state + } + }, error: { _ in + })) + } + }) + + let allPeers = context.engine.data.subscribe( + EngineDataList(allPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))) + ) + + let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData + let signal = combineLatest(queue: .mainQueue(), + presentationData, + statePromise.get(), + allPeers + ) + |> map { presentationData, state, allPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in + let crossfade = false + let animateChanges = false + + //TODO:localize + let title: ItemListControllerTitle + title = .text("Share Folder") + + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: folderInviteLinkListControllerEntries( + presentationData: presentationData, + state: state, + allPeers: allPeers.compactMap { $0 } + ), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges) + + return (controllerState, (listState, arguments)) + } + |> afterDisposed { + actionsDisposable.dispose() + } + + let controller = ItemListController(context: context, state: signal) + controller.navigationPresentation = .modal + controller.willDisappear = { _ in + dismissTooltipsImpl?() + } + controller.didDisappear = { [weak controller] _ in + controller?.clearItemNodesHighlight(animated: true) + } + controller.visibleBottomContentOffsetChanged = { offset in + if case let .known(value) = offset, value < 40.0 { + + } + } + pushControllerImpl = { [weak controller] c in + if let controller = controller { + (controller.navigationController as? NavigationController)?.pushViewController(c, animated: true) + } + } + presentControllerImpl = { [weak controller] c, p in + if let controller = controller { + controller.present(c, in: .window(.root), with: p) + } + } + presentInGlobalOverlayImpl = { [weak controller] c in + if let controller = controller { + controller.presentInGlobalOverlay(c) + } + } + getControllerImpl = { [weak controller] in + return controller + } + dismissTooltipsImpl = { [weak controller] in + controller?.window?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + }) + controller?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + return true + }) + } + return controller +} diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift new file mode 100644 index 0000000000..f3d5d3ad0f --- /dev/null +++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift @@ -0,0 +1,586 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import ItemListUI +import SolidRoundedButtonNode +import AnimatedAvatarSetNode +import ShimmerEffect +import TelegramCore + +private func actionButtonImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(color.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + context.setBlendMode(.clear) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 4.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0))) + }) +} + +public class ItemListFolderInviteLinkItem: ListViewItem, ItemListItem { + let context: AccountContext + let presentationData: ItemListPresentationData + let invite: ExportedChatFolderLink? + let count: Int32 + let peers: [EnginePeer] + let displayButton: Bool + let enableButton: Bool + let buttonTitle: String + let displayImporters: Bool + let buttonColor: UIColor? + public let sectionId: ItemListSectionId + let style: ItemListStyle + let copyAction: (() -> Void)? + let shareAction: (() -> Void)? + let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? + let viewAction: (() -> Void)? + public let tag: ItemListItemTag? + + public init( + context: AccountContext, + presentationData: ItemListPresentationData, + invite: ExportedChatFolderLink?, + count: Int32, + peers: [EnginePeer], + displayButton: Bool, + enableButton: Bool, + buttonTitle: String, + displayImporters: Bool, + buttonColor: UIColor?, + sectionId: ItemListSectionId, + style: ItemListStyle, + copyAction: (() -> Void)?, + shareAction: (() -> Void)?, + contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, + viewAction: (() -> Void)?, + tag: ItemListItemTag? = nil + ) { + self.context = context + self.presentationData = presentationData + self.invite = invite + self.count = count + self.peers = peers + self.displayButton = displayButton + self.enableButton = enableButton + self.buttonTitle = buttonTitle + self.displayImporters = displayImporters + self.buttonColor = buttonColor + self.sectionId = sectionId + self.style = style + self.copyAction = copyAction + self.shareAction = shareAction + self.contextAction = contextAction + self.viewAction = viewAction + self.tag = tag + } + + public 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 = ItemListFolderInviteLinkItemNode() + 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() }) + }) + } + } + } + + public 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 { + if let nodeValue = node() as? ItemListFolderInviteLinkItemNode { + 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() + }) + } + } + } + } + } + + public var selectable: Bool = false +} + +public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let maskNode: ASImageNode + + private let fieldNode: ASImageNode + private let addressNode: TextNode + private let fieldButtonNode: HighlightTrackingButtonNode + private let referenceContainerNode: ContextReferenceContentNode + private let containerNode: ContextControllerSourceNode + private let addressButtonNode: HighlightTrackingButtonNode + private let addressButtonIconNode: ASImageNode + private var addressShimmerNode: ShimmerEffectNode? + private var shareButtonNode: SolidRoundedButtonNode? + + private let avatarsButtonNode: HighlightTrackingButtonNode + private let avatarsContext: AnimatedAvatarSetContext + private var avatarsContent: AnimatedAvatarSetContext.Content? + private let avatarsNode: AnimatedAvatarSetNode + private let invitedPeersNode: TextNode + private var shimmerNode: ShimmerEffectNode? + private var absoluteLocation: (CGRect, CGSize)? + + private let activateArea: AccessibilityAreaNode + + private var item: ItemListFolderInviteLinkItem? + + override public var canBeSelected: Bool { + return false + } + + public var tag: ItemListItemTag? { + return self.item?.tag + } + + public init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.backgroundColor = .white + + self.maskNode = ASImageNode() + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.fieldNode = ASImageNode() + self.fieldNode.displaysAsynchronously = false + self.fieldNode.displayWithoutProcessing = true + + self.addressNode = TextNode() + self.addressNode.isUserInteractionEnabled = false + + self.fieldButtonNode = HighlightTrackingButtonNode() + + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + self.referenceContainerNode = ContextReferenceContentNode() + + self.addressButtonNode = HighlightTrackingButtonNode() + self.addressButtonIconNode = ASImageNode() + self.addressButtonIconNode.contentMode = .center + self.addressButtonIconNode.displaysAsynchronously = false + self.addressButtonIconNode.displayWithoutProcessing = true + + self.avatarsButtonNode = HighlightTrackingButtonNode() + self.avatarsContext = AnimatedAvatarSetContext() + self.avatarsNode = AnimatedAvatarSetNode() + self.invitedPeersNode = TextNode() + + self.activateArea = AccessibilityAreaNode() + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.fieldNode) + self.addSubnode(self.addressNode) + self.addSubnode(self.fieldButtonNode) + self.addSubnode(self.avatarsNode) + self.addSubnode(self.invitedPeersNode) + self.addSubnode(self.avatarsButtonNode) + + self.containerNode.addSubnode(self.referenceContainerNode) + self.referenceContainerNode.addSubnode(self.addressButtonIconNode) + self.referenceContainerNode.addSubnode(self.addressButtonNode) + self.addSubnode(self.containerNode) + + self.addSubnode(self.activateArea) + + self.containerNode.activated = { [weak self] gesture, _ in + if let strongSelf = self, let item = strongSelf.item { + item.contextAction?(strongSelf.referenceContainerNode, gesture) + } + } + + self.fieldButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.addressNode.layer.removeAnimation(forKey: "opacity") + strongSelf.addressNode.alpha = 0.4 + } else { + strongSelf.addressNode.alpha = 1.0 + strongSelf.addressNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.fieldButtonNode.addTarget(self, action: #selector(self.fieldButtonPressed), forControlEvents: .touchUpInside) + + self.addressButtonNode.addTarget(self, action: #selector(self.addressButtonPressed), forControlEvents: .touchUpInside) + self.addressButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.addressButtonIconNode.layer.removeAnimation(forKey: "opacity") + strongSelf.addressButtonIconNode.alpha = 0.4 + } else { + strongSelf.addressButtonIconNode.alpha = 1.0 + strongSelf.addressButtonIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.shareButtonNode?.pressed = { [weak self] in + if let strongSelf = self, let item = strongSelf.item { + item.shareAction?() + } + } + self.avatarsButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.avatarsNode.layer.removeAnimation(forKey: "opacity") + strongSelf.invitedPeersNode.layer.removeAnimation(forKey: "opacity") + strongSelf.avatarsNode.alpha = 0.4 + strongSelf.invitedPeersNode.alpha = 0.4 + } else { + strongSelf.avatarsNode.alpha = 1.0 + strongSelf.invitedPeersNode.alpha = 1.0 + strongSelf.avatarsNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.invitedPeersNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.avatarsButtonNode.addTarget(self, action: #selector(self.avatarsButtonPressed), forControlEvents: .touchUpInside) + } + + @objc private func fieldButtonPressed() { + if let item = self.item { + item.copyAction?() + } + } + + @objc private func addressButtonPressed() { + if let item = self.item { + item.contextAction?(self.referenceContainerNode, nil) + } + } + + @objc private func avatarsButtonPressed() { + if let item = self.item { + item.viewAction?() + } + } + + public func asyncLayout() -> (_ item: ItemListFolderInviteLinkItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let makeAddressLayout = TextNode.asyncLayout(self.addressNode) + let makeInvitedPeersLayout = TextNode.asyncLayout(self.invitedPeersNode) + + let currentItem = self.item + let avatarsContext = self.avatarsContext + + return { item, params, neighbors in + var updatedTheme: PresentationTheme? + if currentItem?.presentationData.theme !== item.presentationData.theme { + updatedTheme = item.presentationData.theme + } + + let contentSize: CGSize + let insets: UIEdgeInsets + let separatorHeight = UIScreenPixel + let itemBackgroundColor: UIColor + let itemSeparatorColor: UIColor + + let leftInset = 16.0 + params.leftInset + let rightInset = 16.0 + params.rightInset + + let titleColor: UIColor + titleColor = item.presentationData.theme.list.itemInputField.primaryColor + + let alignCentrally = !"".isEmpty//!(item.invite?.link?.contains("joinchat") ?? true) + + let addressFont = Font.regular(!alignCentrally && params.width == 320 ? floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0) : item.presentationData.fontSize.itemListBaseFontSize) + let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) + + let constrainedWidth = alignCentrally ? params.width - leftInset - rightInset - 90.0 : params.width - leftInset - rightInset - 60.0 + + let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.invite.flatMap({ $0.link.replacingOccurrences(of: "https://", with: "") }) ?? "", font: addressFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let subtitle: String + let subtitleColor: UIColor + if item.count > 0 { + subtitle = item.presentationData.strings.InviteLink_PeopleJoined(item.count) + subtitleColor = item.presentationData.theme.list.itemAccentColor + } else { + subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone + subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor + } + + let (invitedPeersLayout, invitedPeersApply) = makeInvitedPeersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: titleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let avatarsContent = avatarsContext.update(peers: item.peers, animated: false) + + let verticalInset: CGFloat = 16.0 + let fieldHeight: CGFloat = 52.0 + let fieldSpacing: CGFloat = 16.0 + let buttonHeight: CGFloat = 50.0 + + var height = verticalInset * 2.0 + fieldHeight + fieldSpacing + buttonHeight + 54.0 + + switch item.style { + case .plain: + itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor + itemSeparatorColor = .clear + insets = UIEdgeInsets() + case .blocks: + itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor + insets = itemListNeighborsGroupedInsets(neighbors, params) + } + + if !item.displayImporters { + height -= 57.0 + } + if !item.displayButton { + height -= 63.0 + } + + contentSize = CGSize(width: params.width, height: height) + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in + if let strongSelf = self { + strongSelf.item = item + strongSelf.avatarsContent = avatarsContent + + strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height)) +// strongSelf.activateArea.accessibilityLabel = item.title +// strongSelf.activateArea.accessibilityValue = item.label + strongSelf.activateArea.accessibilityTraits = [] + + if let _ = updatedTheme { + strongSelf.topStripeNode.backgroundColor = itemSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor + strongSelf.backgroundNode.backgroundColor = itemBackgroundColor + strongSelf.fieldNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: item.presentationData.theme.list.itemInputField.backgroundColor) + strongSelf.addressButtonIconNode.image = actionButtonImage(color: item.presentationData.theme.list.itemInputField.controlColor) + } + + let _ = addressApply() + let _ = invitedPeersApply() + + switch item.style { + case .plain: + if strongSelf.backgroundNode.supernode != nil { + strongSelf.backgroundNode.removeFromSupernode() + } + if strongSelf.topStripeNode.supernode != nil { + strongSelf.topStripeNode.removeFromSupernode() + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) + } + if strongSelf.maskNode.supernode != nil { + strongSelf.maskNode.removeFromSupernode() + } + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) + case .blocks: + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners + } + let bottomStripeInset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = leftInset + strongSelf.bottomStripeNode.isHidden = false + default: + bottomStripeInset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners + } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + } + + let fieldFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: CGSize(width: params.width - leftInset - rightInset, height: fieldHeight)) + strongSelf.fieldNode.frame = fieldFrame + strongSelf.fieldButtonNode.frame = fieldFrame + + strongSelf.addressNode.frame = CGRect(origin: CGPoint(x: fieldFrame.minX + (alignCentrally ? floorToScreenPixels((fieldFrame.width - addressLayout.size.width) / 2.0) : 14.0), y: fieldFrame.minY + floorToScreenPixels((fieldFrame.height - addressLayout.size.height) / 2.0) + 1.0), size: addressLayout.size) + + strongSelf.containerNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - 38.0 - 14.0, y: verticalInset), size: CGSize(width: 52.0, height: 52.0)) + strongSelf.addressButtonNode.frame = strongSelf.containerNode.bounds + strongSelf.referenceContainerNode.frame = strongSelf.containerNode.bounds + strongSelf.addressButtonIconNode.frame = strongSelf.containerNode.bounds + + let shareButtonNode: SolidRoundedButtonNode + if let currentShareButtonNode = strongSelf.shareButtonNode { + shareButtonNode = currentShareButtonNode + } else { + let buttonTheme: SolidRoundedButtonTheme + if let buttonColor = item.buttonColor { + buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor) + } else { + buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) + } + shareButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: 50.0, cornerRadius: 11.0) + shareButtonNode.pressed = { [weak self] in + self?.item?.shareAction?() + } + strongSelf.addSubnode(shareButtonNode) + strongSelf.shareButtonNode = shareButtonNode + } + + shareButtonNode.title = item.buttonTitle + + let buttonWidth = contentSize.width - leftInset - rightInset + let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate) + shareButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight) + + var totalWidth = invitedPeersLayout.size.width + var leftOrigin: CGFloat = floorToScreenPixels((params.width - invitedPeersLayout.size.width) / 2.0) + let avatarSpacing: CGFloat = 21.0 + if let avatarsContent = strongSelf.avatarsContent { + let avatarsSize = strongSelf.avatarsNode.update(context: item.context, content: avatarsContent, itemSize: CGSize(width: 32.0, height: 32.0), animated: true, synchronousLoad: true) + + if !avatarsSize.width.isZero { + totalWidth += avatarsSize.width + avatarSpacing + } + + let avatarsNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - totalWidth) / 2.0), y: fieldFrame.maxY + 87.0), size: avatarsSize) + strongSelf.avatarsNode.frame = avatarsNodeFrame + if !avatarsSize.width.isZero { + leftOrigin = avatarsNodeFrame.maxX + avatarSpacing + } + } + + strongSelf.invitedPeersNode.frame = CGRect(origin: CGPoint(x: leftOrigin, y: fieldFrame.maxY + 92.0), size: invitedPeersLayout.size) + + strongSelf.avatarsButtonNode.frame = CGRect(x: floorToScreenPixels((params.width - totalWidth) / 2.0), y: fieldFrame.maxY + 87.0, width: totalWidth, height: 32.0) + strongSelf.avatarsButtonNode.isUserInteractionEnabled = !item.peers.isEmpty && item.invite != nil + + strongSelf.addressButtonNode.isUserInteractionEnabled = item.invite != nil + strongSelf.fieldButtonNode.isUserInteractionEnabled = item.invite != nil + strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0 + + strongSelf.shareButtonNode?.isUserInteractionEnabled = item.enableButton + strongSelf.shareButtonNode?.alpha = item.enableButton ? 1.0 : 0.4 + strongSelf.shareButtonNode?.isHidden = !item.displayButton + strongSelf.avatarsButtonNode.isHidden = !item.displayImporters + strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil + strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil + + if item.invite == nil { + let shimmerNode: ShimmerEffectNode + if let current = strongSelf.shimmerNode { + shimmerNode = current + } else { + shimmerNode = ShimmerEffectNode() + strongSelf.shimmerNode = shimmerNode + strongSelf.insertSubnode(shimmerNode, belowSubnode: strongSelf.fieldNode) + } + shimmerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) + if let (rect, size) = strongSelf.absoluteLocation { + shimmerNode.updateAbsoluteRect(rect, within: size) + } + + let lineWidth: CGFloat = 180.0 + let lineDiameter: CGFloat = 12.0 + let titleFrame = strongSelf.invitedPeersNode.frame + + var shapes: [ShimmerEffectNode.Shape] = [] + shapes.append(.roundedRectLine(startPoint: CGPoint(x: floor(titleFrame.center.x - lineWidth / 2.0), y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: lineWidth, diameter: lineDiameter)) + shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize) + + let addressShimmerNode: ShimmerEffectNode + if let current = strongSelf.addressShimmerNode { + addressShimmerNode = current + } else { + addressShimmerNode = ShimmerEffectNode() + strongSelf.addressShimmerNode = addressShimmerNode + strongSelf.insertSubnode(addressShimmerNode, aboveSubnode: strongSelf.fieldNode) + } + addressShimmerNode.frame = strongSelf.fieldNode.frame.insetBy(dx: 18.0, dy: 0.0) + if let (rect, size) = strongSelf.absoluteLocation { + addressShimmerNode.updateAbsoluteRect(CGRect(x: rect.minX + strongSelf.fieldNode.frame.minX + 18.0, y: rect.minY + strongSelf.fieldNode.frame.minY, width: strongSelf.fieldNode.frame.width - 18.0 * 2.0, height: strongSelf.fieldNode.frame.height), within: size) + } + + let addressLineWidth: CGFloat = strongSelf.fieldNode.frame.width - 100.0 + var addressShapes: [ShimmerEffectNode.Shape] = [] + addressShapes.append(.roundedRectLine(startPoint: CGPoint(x: floor(addressShimmerNode.frame.width / 2.0 - addressLineWidth / 2.0), y: 16.0 + floor((22.0 - lineDiameter) / 2.0)), width: addressLineWidth, diameter: lineDiameter)) + addressShimmerNode.update(backgroundColor: item.presentationData.theme.list.itemInputField.backgroundColor, foregroundColor: item.presentationData.theme.list.itemInputField.controlColor.mixedWith(item.presentationData.theme.list.itemInputField.backgroundColor, alpha: 0.7), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: addressShapes, size: addressShimmerNode.frame.size) + + } else { + if let shimmerNode = strongSelf.shimmerNode { + strongSelf.shimmerNode = nil + shimmerNode.removeFromSupernode() + } + if let shimmerNode = strongSelf.addressShimmerNode { + strongSelf.shimmerNode = nil + shimmerNode.removeFromSupernode() + } + } + } + }) + } + } + + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + var rect = rect + rect.origin.y += self.insets.top + self.absoluteLocation = (rect, containerSize) + if let shimmerNode = self.addressShimmerNode { + shimmerNode.updateAbsoluteRect(CGRect(x: rect.minX + self.fieldNode.frame.minX + 18.0, y: rect.minY + self.fieldNode.frame.minY, width: self.fieldNode.frame.width - 18.0 * 2.0, height: self.fieldNode.frame.height), within: containerSize) + } + if let shimmerNode = self.shimmerNode { + shimmerNode.updateAbsoluteRect(rect, within: containerSize) + } + } +} diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift new file mode 100644 index 0000000000..3ae4917d02 --- /dev/null +++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift @@ -0,0 +1,757 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import ItemListUI +import ShimmerEffect +import TelegramCore + +private enum ItemBackgroundColor: Equatable { + case blue + case green + case yellow + case red + case gray + + var colors: (top: UIColor, bottom: UIColor, text: UIColor) { + switch self { + case .blue: + return (UIColor(rgb: 0x00b5f7), UIColor(rgb: 0x00b2f6), UIColor(rgb: 0xa7f4ff)) + case .green: + return (UIColor(rgb: 0x4aca62), UIColor(rgb: 0x43c85c), UIColor(rgb: 0xc5ffe6)) + case .yellow: + return (UIColor(rgb: 0xf8a953), UIColor(rgb: 0xf7a64e), UIColor(rgb: 0xfeffd7)) + case .red: + return (UIColor(rgb: 0xf2656a), UIColor(rgb: 0xf25f65), UIColor(rgb: 0xffd3de)) + case .gray: + return (UIColor(rgb: 0xa8b2bb), UIColor(rgb: 0xa2abb4), UIColor(rgb: 0xe3e6e8)) + } + } +} + +public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem { + let presentationData: ItemListPresentationData + let invite: ExportedChatFolderLink? + let share: Bool + public let sectionId: ItemListSectionId + let style: ItemListStyle + let tapAction: ((ExportedChatFolderLink) -> Void)? + let contextAction: ((ExportedChatFolderLink, ASDisplayNode, ContextGesture?) -> Void)? + public let tag: ItemListItemTag? + + public init( + presentationData: ItemListPresentationData, + invite: ExportedChatFolderLink?, + share: Bool, + sectionId: ItemListSectionId, + style: ItemListStyle, + tapAction: ((ExportedChatFolderLink) -> Void)?, + contextAction: ((ExportedChatFolderLink, ASDisplayNode, ContextGesture?) -> Void)?, + tag: ItemListItemTag? = nil + ) { + self.presentationData = presentationData + self.invite = invite + self.share = share + self.sectionId = sectionId + self.style = style + self.tapAction = tapAction + self.contextAction = contextAction + self.tag = tag + } + + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + var firstWithHeader = false + var last = false + if self.style == .plain { + if previousItem == nil { + firstWithHeader = true + } + if nextItem == nil { + last = true + } + } + let node = ItemListFolderInviteLinkListItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), firstWithHeader, last) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + public 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 { + if let nodeValue = node() as? ItemListFolderInviteLinkListItemNode { + let makeLayout = nodeValue.asyncLayout() + + async { + var firstWithHeader = false + var last = false + if self.style == .plain { + if previousItem == nil { + firstWithHeader = true + } + if nextItem == nil { + last = true + } + } + + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), firstWithHeader, last) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } + } + + public var selectable: Bool = true + + public func selected(listView: ListView) { + listView.clearHighlightAnimated(true) + if let invite = self.invite { + self.tapAction?(invite) + } + } +} + +public class ItemListFolderInviteLinkListItemNode: ListViewItemNode, ItemListItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode + + private let extractedBackgroundImageNode: ASImageNode + + private let containerNode: ContextControllerSourceNode + private let contextSourceNode: ContextExtractedContentContainingNode + + private var extractedRect: CGRect? + private var nonExtractedRect: CGRect? + + private let offsetContainerNode: ASDisplayNode + + private let iconBackgroundNode: ASDisplayNode + private let iconNode: ASImageNode + private var timerNode: TimerNode? + + private let titleNode: TextNode + private let subtitleNode: TextNode + + private var placeholderNode: ShimmerEffectNode? + private var absoluteLocation: (CGRect, CGSize)? + + private var currentColor: ItemBackgroundColor? + private var layoutParams: (ItemListFolderInviteLinkListItem, ListViewItemLayoutParams, ItemListNeighbors, Bool, Bool)? + + public var tag: ItemListItemTag? + + public init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + self.maskNode = ASImageNode() + + self.extractedBackgroundImageNode = ASImageNode() + self.extractedBackgroundImageNode.displaysAsynchronously = false + self.extractedBackgroundImageNode.alpha = 0.0 + + self.contextSourceNode = ContextExtractedContentContainingNode() + self.containerNode = ContextControllerSourceNode() + + self.offsetContainerNode = ASDisplayNode() + + self.iconBackgroundNode = ASDisplayNode() + self.iconBackgroundNode.setLayerBlock { () -> CALayer in + return CAShapeLayer() + } + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + self.iconNode.contentMode = .center + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + self.subtitleNode = TextNode() + self.subtitleNode.isUserInteractionEnabled = false + self.subtitleNode.contentMode = .left + self.subtitleNode.contentsScale = UIScreen.main.scale + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.isAccessibilityElement = true + + self.containerNode.addSubnode(self.contextSourceNode) + self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode + self.addSubnode(self.containerNode) + + self.contextSourceNode.contentNode.addSubnode(self.extractedBackgroundImageNode) + self.contextSourceNode.contentNode.addSubnode(self.offsetContainerNode) + + self.offsetContainerNode.addSubnode(self.iconBackgroundNode) + self.offsetContainerNode.addSubnode(self.iconNode) + self.offsetContainerNode.addSubnode(self.titleNode) + self.offsetContainerNode.addSubnode(self.subtitleNode) + + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self, let item = strongSelf.layoutParams?.0, let invite = item.invite, let contextAction = item.contextAction else { + gesture.cancel() + return + } + contextAction(invite, strongSelf.contextSourceNode, gesture) + } + + self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let strongSelf = self, let item = strongSelf.layoutParams?.0 else { + return + } + + if isExtracted { + strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.list.plainBackgroundColor) + } + + if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect { + let rect = isExtracted ? extractedRect : nonExtractedRect + transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect) + } + + transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.0 : 0.0, y: 0.0)) + transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in + if !isExtracted { + self?.extractedBackgroundImageNode.image = nil + } + }) + } + } + + public override func didLoad() { + super.didLoad() + + if let shapeLayer = self.iconBackgroundNode.layer as? CAShapeLayer { + shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 0.0, y: 0.0, width: 40.0, height: 40.0)).cgPath + } + } + + public func asyncLayout() -> (_ item: ItemListFolderInviteLinkListItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors, _ firstWithHeader: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) + + let currentItem = self.layoutParams?.0 + + return { item, params, neighbors, firstWithHeader, last in + var updatedTheme: PresentationTheme? + + let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) + let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + + if currentItem?.presentationData.theme !== item.presentationData.theme { + updatedTheme = item.presentationData.theme + } + + let color: ItemBackgroundColor + let nextColor: ItemBackgroundColor + let transitionFraction: CGFloat + + color = .green + nextColor = .yellow + transitionFraction = 1.0 + + let topColor = color.colors.top + let nextTopColor = nextColor.colors.top + let iconColor: UIColor + if let _ = item.invite { + if case .blue = color { + iconColor = item.presentationData.theme.list.itemAccentColor + } else { + iconColor = nextTopColor.mixedWith(topColor, alpha: transitionFraction) + } + } else { + iconColor = item.presentationData.theme.list.mediaPlaceholderColor + } + + let inviteLink = item.invite?.link.replacingOccurrences(of: "https://", with: "") ?? "" + var titleText = inviteLink + var subtitleText: String = "" + + if let invite = item.invite { + if !invite.title.isEmpty { + titleText = invite.title + } + + //TODO:localize + if invite.peerIds.count == 1 { + subtitleText = "includes 1 chat" + } else { + subtitleText = "includes \(invite.peerIds.count) chats" + } + } else { + titleText = " " + subtitleText = " " + } + + let titleAttributedString = NSAttributedString(string: titleText, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) + let subtitleAttributedString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) + + let leftInset: CGFloat = 65.0 + params.leftInset + let rightInset: CGFloat = 16.0 + params.rightInset + let verticalInset: CGFloat = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0 + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let titleSpacing: CGFloat = 1.0 + + let minHeight: CGFloat = titleLayout.size.height + verticalInset * 2.0 + let rawHeight: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + + var insets: UIEdgeInsets + let itemBackgroundColor: UIColor + let itemSeparatorColor: UIColor + switch item.style { + case .plain: + itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor + itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor + insets = itemListNeighborsPlainInsets(neighbors) + insets.top = firstWithHeader ? 29.0 : 0.0 + insets.bottom = 0.0 + case .blocks: + itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor + insets = itemListNeighborsGroupedInsets(neighbors, params) + } + + let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) + let separatorHeight = UIScreenPixel + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.layoutParams = (item, params, neighbors, firstWithHeader, last) + + strongSelf.accessibilityLabel = titleAttributedString.string + strongSelf.accessibilityValue = subtitleAttributedString.string + + strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) + strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) + strongSelf.offsetContainerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) + strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) + strongSelf.containerNode.isGestureEnabled = item.contextAction != nil + + let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height)) + let extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0) + strongSelf.extractedRect = extractedRect + strongSelf.nonExtractedRect = nonExtractedRect + + if strongSelf.contextSourceNode.isExtractedToContextPreview { + strongSelf.extractedBackgroundImageNode.frame = extractedRect + } else { + strongSelf.extractedBackgroundImageNode.frame = nonExtractedRect + } + strongSelf.contextSourceNode.contentRect = extractedRect + + if let layer = strongSelf.iconBackgroundNode.layer as? CAShapeLayer { + layer.fillColor = iconColor.cgColor + } + + if let _ = updatedTheme { + strongSelf.topStripeNode.backgroundColor = itemSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor + strongSelf.backgroundNode.backgroundColor = itemBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + + strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) + } + + let transition = ContainedViewLayoutTransition.immediate + + let _ = titleApply() + let _ = subtitleApply() + + switch item.style { + case .plain: + if strongSelf.backgroundNode.supernode != nil { + strongSelf.backgroundNode.removeFromSupernode() + } + if strongSelf.topStripeNode.supernode != nil { + strongSelf.topStripeNode.removeFromSupernode() + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) + } + if strongSelf.maskNode.supernode != nil { + strongSelf.maskNode.removeFromSupernode() + } + + let stripeInset: CGFloat + if case .none = neighbors.bottom { + stripeInset = 0.0 + } else { + stripeInset = leftInset + } + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: stripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - stripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.isHidden = last + case .blocks: + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners + } + let bottomStripeInset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = leftInset + strongSelf.bottomStripeNode.isHidden = false + default: + bottomStripeInset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners + } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + } + + let iconSize: CGSize = CGSize(width: 40.0, height: 40.0) + let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + 12.0, y: floorToScreenPixels((layout.contentSize.height - iconSize.height) / 2.0)), size: iconSize) + strongSelf.iconBackgroundNode.bounds = CGRect(origin: CGPoint(), size: iconSize) + strongSelf.iconBackgroundNode.position = iconFrame.center + strongSelf.iconNode.frame = iconFrame + + transition.updateTransformScale(node: strongSelf.iconBackgroundNode, scale: 1.0) + + strongSelf.timerNode?.frame = iconFrame.insetBy(dx: -5.0, dy: -5.0) + + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.subtitleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleLayout.size.height + titleSpacing), size: subtitleLayout.size)) + + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)) + + if item.invite == nil { + let shimmerNode: ShimmerEffectNode + if let current = strongSelf.placeholderNode { + shimmerNode = current + } else { + shimmerNode = ShimmerEffectNode() + strongSelf.placeholderNode = shimmerNode + strongSelf.addSubnode(shimmerNode) + } + shimmerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) + if let (rect, size) = strongSelf.absoluteLocation { + shimmerNode.updateAbsoluteRect(rect, within: size) + } + + var shapes: [ShimmerEffectNode.Shape] = [] + + let titleLineWidth: CGFloat = 180.0 + let subtitleLineWidth: CGFloat = 60.0 + let lineDiameter: CGFloat = 10.0 + + let iconFrame = strongSelf.iconBackgroundNode.frame + shapes.append(.circle(iconFrame)) + + let titleFrame = strongSelf.titleNode.frame + shapes.append(.roundedRectLine(startPoint: CGPoint(x: titleFrame.minX, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: titleLineWidth, diameter: lineDiameter)) + + let subtitleFrame = strongSelf.subtitleNode.frame + shapes.append(.roundedRectLine(startPoint: CGPoint(x: subtitleFrame.minX, y: subtitleFrame.minY + floor((subtitleFrame.height - lineDiameter) / 2.0)), width: subtitleLineWidth, diameter: lineDiameter)) + + shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize) + } else if let shimmerNode = strongSelf.placeholderNode { + strongSelf.placeholderNode = nil + shimmerNode.removeFromSupernode() + } + } + }) + } + } + + override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + if highlighted { + self.highlightedBackgroundNode.alpha = 1.0 + if self.highlightedBackgroundNode.supernode == nil { + var anchorNode: ASDisplayNode? + if self.bottomStripeNode.supernode != nil { + anchorNode = self.bottomStripeNode + } else if self.topStripeNode.supernode != nil { + anchorNode = self.topStripeNode + } else if self.backgroundNode.supernode != nil { + anchorNode = self.backgroundNode + } + if let anchorNode = anchorNode { + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) + } else { + self.addSubnode(self.highlightedBackgroundNode) + } + } + } else { + if self.highlightedBackgroundNode.supernode != nil { + if animated { + self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in + if let strongSelf = self { + if completed { + strongSelf.highlightedBackgroundNode.removeFromSupernode() + } + } + }) + self.highlightedBackgroundNode.alpha = 0.0 + } else { + self.highlightedBackgroundNode.removeFromSupernode() + } + } + } + } + + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + var rect = rect + rect.origin.y += self.insets.top + self.absoluteLocation = (rect, containerSize) + if let shimmerNode = self.placeholderNode { + shimmerNode.updateAbsoluteRect(rect, within: containerSize) + } + } +} + +private struct ContentParticle { + var position: CGPoint + var direction: CGPoint + var velocity: CGFloat + var alpha: CGFloat + var lifetime: Double + var beginTime: Double + + init(position: CGPoint, direction: CGPoint, velocity: CGFloat, alpha: CGFloat, lifetime: Double, beginTime: Double) { + self.position = position + self.direction = direction + self.velocity = velocity + self.alpha = alpha + self.lifetime = lifetime + self.beginTime = beginTime + } +} + +private final class TimerNode: ASDisplayNode { + enum Value: Equatable { + case timestamp(creation: Int32, deadline: Int32) + case fraction(CGFloat) + } + private struct Params: Equatable { + var color: UIColor + var value: Value + } + + private let hierarchyTrackingNode: HierarchyTrackingNode + private var inHierarchyValue: Bool = false + + private var animator: ConstantDisplayLinkAnimator? + private let contentNode: ASDisplayNode + private var particles: [ContentParticle] = [] + + private var currentParams: Params? + + var reachedTimeout: (() -> Void)? + + override init() { + var updateInHierarchy: ((Bool) -> Void)? + self.hierarchyTrackingNode = HierarchyTrackingNode({ value in + updateInHierarchy?(value) + }) + + self.contentNode = ASDisplayNode() + + super.init() + + self.addSubnode(self.contentNode) + + updateInHierarchy = { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.inHierarchyValue = value + strongSelf.animator?.isPaused = value + } + } + + deinit { + self.animator?.invalidate() + } + + func update(color: UIColor, value: Value) { + let params = Params( + color: color, + value: value + ) + self.currentParams = params + + self.updateValues() + } + + private func updateValues() { + guard let params = self.currentParams else { + return + } + + let color = params.color + + let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + var fraction: CGFloat + switch params.value { + case let .fraction(value): + fraction = value + case let .timestamp(creation, deadline): + fraction = CGFloat(deadline - currentTimestamp) / CGFloat(deadline - creation) + } + fraction = max(0.0001, 1.0 - max(0.0, min(1.0, fraction))) + + let image: UIImage? + + let diameter: CGFloat = 42.0 + let inset: CGFloat = 8.0 + let lineWidth: CGFloat = 2.0 + + let timestamp = CACurrentMediaTime() + + let center = CGPoint(x: (diameter + inset) / 2.0, y: (diameter + inset) / 2.0) + let radius: CGFloat = (diameter - lineWidth / 2.0) / 2.0 + + let startAngle: CGFloat = -CGFloat.pi / 2.0 + let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction + + let sparks = fraction > 0.05 && fraction != 1.0 + if sparks { + let v = CGPoint(x: sin(endAngle), y: -cos(endAngle)) + let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y) + + let dt: CGFloat = 1.0 / 60.0 + var removeIndices: [Int] = [] + for i in 0 ..< self.particles.count { + let currentTime = timestamp - self.particles[i].beginTime + if currentTime > self.particles[i].lifetime { + removeIndices.append(i) + } else { + let input: CGFloat = CGFloat(currentTime / self.particles[i].lifetime) + let decelerated: CGFloat = (1.0 - (1.0 - input) * (1.0 - input)) + self.particles[i].alpha = 1.0 - decelerated + + var p = self.particles[i].position + let d = self.particles[i].direction + let v = self.particles[i].velocity + p = CGPoint(x: p.x + d.x * v * dt, y: p.y + d.y * v * dt) + self.particles[i].position = p + } + } + + for i in removeIndices.reversed() { + self.particles.remove(at: i) + } + + let newParticleCount = 1 + for _ in 0 ..< newParticleCount { + let degrees: CGFloat = CGFloat(arc4random_uniform(140)) - 40.0 + let angle: CGFloat = degrees * CGFloat.pi / 180.0 + + let direction = CGPoint(x: v.x * cos(angle) - v.y * sin(angle), y: v.x * sin(angle) + v.y * cos(angle)) + let velocity = (20.0 + (CGFloat(arc4random()) / CGFloat(UINT32_MAX)) * 4.0) * 0.3 + + let lifetime = Double(0.4 + CGFloat(arc4random_uniform(100)) * 0.01) + + let particle = ContentParticle(position: c, direction: direction, velocity: velocity, alpha: 1.0, lifetime: lifetime, beginTime: timestamp) + self.particles.append(particle) + } + } + + image = generateImage(CGSize(width: diameter + inset, height: diameter + inset), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(color.cgColor) + context.setFillColor(color.cgColor) + context.setLineWidth(lineWidth) + context.setLineCap(.round) + + let path = CGMutablePath() + path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true) + context.addPath(path) + context.strokePath() + + if sparks { + for particle in self.particles { + let size: CGFloat = 2.0 + context.setAlpha(particle.alpha) + context.fillEllipse(in: CGRect(origin: CGPoint(x: particle.position.x - size / 2.0, y: particle.position.y - size / 2.0), size: CGSize(width: size, height: size))) + } + } + }) + + self.contentNode.contents = image?.cgImage + if let image = image { + self.contentNode.frame = CGRect(origin: CGPoint(), size: image.size) + } + + if fraction <= .ulpOfOne { + self.animator?.invalidate() + self.animator = nil + } else { + if self.animator == nil { + let animator = ConstantDisplayLinkAnimator(update: { [weak self] in + self?.updateValues() + }) + self.animator = animator + animator.isPaused = self.inHierarchyValue + } + } + } +} diff --git a/submodules/ItemListPeerItem/BUILD b/submodules/ItemListPeerItem/BUILD index a2af81dd51..784835efe7 100644 --- a/submodules/ItemListPeerItem/BUILD +++ b/submodules/ItemListPeerItem/BUILD @@ -26,6 +26,7 @@ swift_library( "//submodules/AccountContext:AccountContext", "//submodules/ComponentFlow:ComponentFlow", "//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent", + "//submodules/CheckNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index 06471c28f0..1ce9cd8e6e 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -16,6 +16,7 @@ import ContextUI import AccountContext import ComponentFlow import EmojiStatusComponent +import CheckNode private final class ShimmerEffectNode: ASDisplayNode { private var currentBackgroundColor: UIColor? @@ -262,6 +263,7 @@ public struct ItemListPeerItemSwitch { public enum ItemListPeerItemSwitchStyle { case standard case check + case leftCheck } public enum ItemListPeerItemAliasHandling { @@ -474,6 +476,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo private var credibilityIconView: ComponentHostView? private var switchNode: SwitchNode? private var checkNode: ASImageNode? + private var leftCheckNode: CheckNode? private var shimmerNode: LoadingShimmerNode? private var absoluteLocation: (CGRect, CGSize)? @@ -736,6 +739,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo peerRevealOptions = [] } + var additionalLeftInset: CGFloat = 0.0 + var leftInset: CGFloat = params.leftInset var rightInset: CGFloat = params.rightInset let switchSize = CGSize(width: 51.0, height: 31.0) var checkImage: UIImage? @@ -755,6 +760,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } rightInset += 24.0 currentSwitchNode = nil + case .leftCheck: + additionalLeftInset += 40.0 + leftInset += additionalLeftInset + currentSwitchNode = nil + currentCheckNode = nil } } else { currentSwitchNode = nil @@ -842,7 +852,6 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo break } - let leftInset: CGFloat let verticalInset: CGFloat let verticalOffset: CGFloat let avatarSize: CGFloat @@ -856,7 +865,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } verticalOffset = 0.0 avatarSize = 31.0 - leftInset = 59.0 + params.leftInset + leftInset += 59.0 avatarFontSize = floor(31.0 * 16.0 / 37.0) case .peerList: if case .none = item.text { @@ -866,7 +875,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } verticalOffset = 0.0 avatarSize = 40.0 - leftInset = 65.0 + params.leftInset + leftInset += 65.0 avatarFontSize = floor(40.0 * 16.0 / 37.0) } @@ -1234,9 +1243,27 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo strongSelf.labelBadgeNode.frame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - badgeWidth, y: labelFrame.minY - 1.0), size: CGSize(width: badgeWidth, height: badgeDiameter)) - let avatarFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0, y: floorToScreenPixels((layout.contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)) + let avatarFrame = CGRect(origin: CGPoint(x: params.leftInset + additionalLeftInset + revealOffset + editingOffset + 15.0, y: floorToScreenPixels((layout.contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)) transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame) + if let switchValue = item.switchValue, case .leftCheck = switchValue.style { + let leftCheckNode: CheckNode + if let current = strongSelf.leftCheckNode { + leftCheckNode = current + } else { + leftCheckNode = CheckNode(theme: CheckNodeTheme(theme: item.presentationData.theme, style: .plain)) + strongSelf.leftCheckNode = leftCheckNode + strongSelf.avatarNode.supernode?.addSubnode(leftCheckNode) + } + leftCheckNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 16.0, y: floor((layout.contentSize.height - 22.0) / 2.0)), size: CGSize(width: 22.0, height: 22.0)) + leftCheckNode.setSelected(switchValue.value, animated: animated) + } else { + if let leftCheckNode = strongSelf.leftCheckNode { + strongSelf.leftCheckNode = nil + leftCheckNode.removeFromSupernode() + } + } + if let threadInfo = item.threadInfo { let threadIconSize = floor(avatarSize * 0.9) let threadIconFrame = CGRect(origin: CGPoint(x: avatarFrame.minX + floor((avatarFrame.width - threadIconSize) / 2.0), y: avatarFrame.minY + floor((avatarFrame.height - threadIconSize) / 2.0)), size: CGSize(width: threadIconSize, height: threadIconSize)) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 69d562e992..2c703c1ebd 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -3,6 +3,8 @@ public enum Api { public enum account {} public enum auth {} public enum channels {} + public enum communities {} + public enum community {} public enum contacts {} public enum help {} public enum messages {} @@ -20,6 +22,7 @@ public enum Api { public enum auth {} public enum bots {} public enum channels {} + public enum communities {} public enum contacts {} public enum folders {} public enum help {} @@ -190,6 +193,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { 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[-665432009] = { return Api.DialogFilter.parse_dialogFilterCommunity($0) } dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) } dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) } dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) } @@ -234,6 +238,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[-1350894801] = { return Api.ExportedCommunityInvite.parse_exportedCommunityInvite($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) } @@ -285,6 +290,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1736378792] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordEmpty($0) } dict[-763367294] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordSRP($0) } dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) } + dict[450955169] = { return Api.InputCommunity.parse_inputCommunityDialogFilter($0) } dict[-208488460] = { return Api.InputContact.parse_inputPhoneContact($0) } dict[-55902537] = { return Api.InputDialogPeer.parse_inputDialogPeer($0) } dict[1684014375] = { return Api.InputDialogPeer.parse_inputDialogPeerFolder($0) } @@ -990,6 +996,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) } dict[-266911767] = { return Api.channels.ChannelParticipants.parse_channelParticipantsNotModified($0) } dict[-191450938] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) } + dict[1805101290] = { return Api.communities.ExportedCommunityInvite.parse_exportedCommunityInvite($0) } + dict[-2662489] = { return Api.communities.ExportedInvites.parse_exportedInvites($0) } + dict[408604768] = { return Api.community.CommunityInvite.parse_communityInvite($0) } + dict[74184410] = { return Api.community.CommunityInvite.parse_communityInviteAlready($0) } dict[182326673] = { return Api.contacts.Blocked.parse_blocked($0) } dict[-513392236] = { return Api.contacts.Blocked.parse_blockedSlice($0) } dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) } @@ -1341,6 +1351,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.ExportedChatInvite: _1.serialize(buffer, boxed) + case let _1 as Api.ExportedCommunityInvite: + _1.serialize(buffer, boxed) case let _1 as Api.ExportedContactToken: _1.serialize(buffer, boxed) case let _1 as Api.ExportedMessageLink: @@ -1397,6 +1409,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.InputClientProxy: _1.serialize(buffer, boxed) + case let _1 as Api.InputCommunity: + _1.serialize(buffer, boxed) case let _1 as Api.InputContact: _1.serialize(buffer, boxed) case let _1 as Api.InputDialogPeer: @@ -1783,6 +1797,12 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.channels.SendAsPeers: _1.serialize(buffer, boxed) + case let _1 as Api.communities.ExportedCommunityInvite: + _1.serialize(buffer, boxed) + case let _1 as Api.communities.ExportedInvites: + _1.serialize(buffer, boxed) + case let _1 as Api.community.CommunityInvite: + _1.serialize(buffer, boxed) case let _1 as Api.contacts.Blocked: _1.serialize(buffer, boxed) case let _1 as Api.contacts.Contacts: diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index a008cd6877..41fcc1bd57 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -850,6 +850,224 @@ public extension Api.channels { } } +public extension Api.communities { + enum ExportedCommunityInvite: TypeConstructorDescription { + case exportedCommunityInvite(filter: Api.DialogFilter, invite: Api.ExportedCommunityInvite) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedCommunityInvite(let filter, let invite): + if boxed { + buffer.appendInt32(1805101290) + } + filter.serialize(buffer, true) + invite.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedCommunityInvite(let filter, let invite): + return ("exportedCommunityInvite", [("filter", filter as Any), ("invite", invite as Any)]) + } + } + + public static func parse_exportedCommunityInvite(_ reader: BufferReader) -> ExportedCommunityInvite? { + var _1: Api.DialogFilter? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.DialogFilter + } + var _2: Api.ExportedCommunityInvite? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ExportedCommunityInvite + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.communities.ExportedCommunityInvite.exportedCommunityInvite(filter: _1!, invite: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.communities { + enum ExportedInvites: TypeConstructorDescription { + case exportedInvites(invites: [Api.ExportedCommunityInvite], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedInvites(let invites, let chats, let users): + if boxed { + buffer.appendInt32(-2662489) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(invites.count)) + for item in invites { + 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 + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedInvites(let invites, let chats, let users): + return ("exportedInvites", [("invites", invites as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_exportedInvites(_ reader: BufferReader) -> ExportedInvites? { + var _1: [Api.ExportedCommunityInvite]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedCommunityInvite.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.communities.ExportedInvites.exportedInvites(invites: _1!, chats: _2!, users: _3!) + } + else { + return nil + } + } + + } +} +public extension Api.community { + enum CommunityInvite: TypeConstructorDescription { + case communityInvite(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) + case communityInviteAlready(filterId: Int32, missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .communityInvite(let peers, let chats, let users): + if boxed { + buffer.appendInt32(408604768) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + 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 .communityInviteAlready(let filterId, let missingPeers, let chats, let users): + if boxed { + buffer.appendInt32(74184410) + } + serializeInt32(filterId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(missingPeers.count)) + for item in missingPeers { + 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 + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .communityInvite(let peers, let chats, let users): + return ("communityInvite", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) + case .communityInviteAlready(let filterId, let missingPeers, let chats, let users): + return ("communityInviteAlready", [("filterId", filterId as Any), ("missingPeers", missingPeers as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_communityInvite(_ reader: BufferReader) -> CommunityInvite? { + var _1: [Api.Peer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.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.community.CommunityInvite.communityInvite(peers: _1!, chats: _2!, users: _3!) + } + else { + return nil + } + } + public static func parse_communityInviteAlready(_ reader: BufferReader) -> CommunityInvite? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Peer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.community.CommunityInvite.communityInviteAlready(filterId: _1!, missingPeers: _2!, chats: _3!, users: _4!) + } + else { + return nil + } + } + + } +} public extension Api.contacts { enum Blocked: TypeConstructorDescription { case blocked(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) @@ -962,143 +1180,3 @@ public extension Api.contacts { } } -public extension Api.contacts { - enum Contacts: TypeConstructorDescription { - case contacts(contacts: [Api.Contact], savedCount: Int32, users: [Api.User]) - case contactsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .contacts(let contacts, let savedCount, let users): - if boxed { - buffer.appendInt32(-353862078) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(contacts.count)) - for item in contacts { - item.serialize(buffer, true) - } - serializeInt32(savedCount, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .contactsNotModified: - if boxed { - buffer.appendInt32(-1219778094) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .contacts(let contacts, let savedCount, let users): - return ("contacts", [("contacts", contacts as Any), ("savedCount", savedCount as Any), ("users", users as Any)]) - case .contactsNotModified: - return ("contactsNotModified", []) - } - } - - public static func parse_contacts(_ reader: BufferReader) -> Contacts? { - var _1: [Api.Contact]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Contact.self) - } - var _2: Int32? - _2 = reader.readInt32() - 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.Contacts.contacts(contacts: _1!, savedCount: _2!, users: _3!) - } - else { - return nil - } - } - public static func parse_contactsNotModified(_ reader: BufferReader) -> Contacts? { - return Api.contacts.Contacts.contactsNotModified - } - - } -} -public extension Api.contacts { - enum Found: TypeConstructorDescription { - case found(myResults: [Api.Peer], results: [Api.Peer], chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .found(let myResults, let results, let chats, let users): - if boxed { - buffer.appendInt32(-1290580579) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(myResults.count)) - for item in myResults { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results.count)) - for item in results { - 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 - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .found(let myResults, let results, let chats, let users): - return ("found", [("myResults", myResults as Any), ("results", results as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_found(_ reader: BufferReader) -> Found? { - var _1: [Api.Peer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _2: [Api.Peer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.Found.found(myResults: _1!, results: _2!, chats: _3!, users: _4!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 2cd6480b05..1a273cf5cc 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -1,3 +1,143 @@ +public extension Api.contacts { + enum Contacts: TypeConstructorDescription { + case contacts(contacts: [Api.Contact], savedCount: Int32, users: [Api.User]) + case contactsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .contacts(let contacts, let savedCount, let users): + if boxed { + buffer.appendInt32(-353862078) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(contacts.count)) + for item in contacts { + item.serialize(buffer, true) + } + serializeInt32(savedCount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .contactsNotModified: + if boxed { + buffer.appendInt32(-1219778094) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .contacts(let contacts, let savedCount, let users): + return ("contacts", [("contacts", contacts as Any), ("savedCount", savedCount as Any), ("users", users as Any)]) + case .contactsNotModified: + return ("contactsNotModified", []) + } + } + + public static func parse_contacts(_ reader: BufferReader) -> Contacts? { + var _1: [Api.Contact]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Contact.self) + } + var _2: Int32? + _2 = reader.readInt32() + 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.Contacts.contacts(contacts: _1!, savedCount: _2!, users: _3!) + } + else { + return nil + } + } + public static func parse_contactsNotModified(_ reader: BufferReader) -> Contacts? { + return Api.contacts.Contacts.contactsNotModified + } + + } +} +public extension Api.contacts { + enum Found: TypeConstructorDescription { + case found(myResults: [Api.Peer], results: [Api.Peer], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .found(let myResults, let results, let chats, let users): + if boxed { + buffer.appendInt32(-1290580579) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(myResults.count)) + for item in myResults { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(results.count)) + for item in results { + 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 + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .found(let myResults, let results, let chats, let users): + return ("found", [("myResults", myResults as Any), ("results", results as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_found(_ reader: BufferReader) -> Found? { + var _1: [Api.Peer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + var _2: [Api.Peer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.contacts.Found.found(myResults: _1!, results: _2!, chats: _3!, users: _4!) + } + else { + return nil + } + } + + } +} public extension Api.contacts { enum ImportedContacts: TypeConstructorDescription { case importedContacts(imported: [Api.ImportedContact], popularInvites: [Api.PopularContact], retryContacts: [Int64], users: [Api.User]) @@ -1190,141 +1330,3 @@ public extension Api.help { } } -public extension Api.messages { - enum AffectedFoundMessages: TypeConstructorDescription { - case affectedFoundMessages(pts: Int32, ptsCount: Int32, offset: Int32, messages: [Int32]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .affectedFoundMessages(let pts, let ptsCount, let offset, let messages): - if boxed { - buffer.appendInt32(-275956116) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .affectedFoundMessages(let pts, let ptsCount, let offset, let messages): - return ("affectedFoundMessages", [("pts", pts as Any), ("ptsCount", ptsCount as Any), ("offset", offset as Any), ("messages", messages as Any)]) - } - } - - public static func parse_affectedFoundMessages(_ reader: BufferReader) -> AffectedFoundMessages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Int32]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.AffectedFoundMessages.affectedFoundMessages(pts: _1!, ptsCount: _2!, offset: _3!, messages: _4!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum AffectedHistory: TypeConstructorDescription { - case affectedHistory(pts: Int32, ptsCount: Int32, offset: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .affectedHistory(let pts, let ptsCount, let offset): - if boxed { - buffer.appendInt32(-1269012015) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .affectedHistory(let pts, let ptsCount, let offset): - return ("affectedHistory", [("pts", pts as Any), ("ptsCount", ptsCount as Any), ("offset", offset as Any)]) - } - } - - public static func parse_affectedHistory(_ reader: BufferReader) -> AffectedHistory? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.AffectedHistory.affectedHistory(pts: _1!, ptsCount: _2!, offset: _3!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum AffectedMessages: TypeConstructorDescription { - case affectedMessages(pts: Int32, ptsCount: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .affectedMessages(let pts, let ptsCount): - if boxed { - buffer.appendInt32(-2066640507) - } - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .affectedMessages(let pts, let ptsCount): - return ("affectedMessages", [("pts", pts as Any), ("ptsCount", ptsCount as Any)]) - } - } - - public static func parse_affectedMessages(_ reader: BufferReader) -> AffectedMessages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AffectedMessages.affectedMessages(pts: _1!, ptsCount: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index c18340f6c2..f1029eb040 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -1,3 +1,141 @@ +public extension Api.messages { + enum AffectedFoundMessages: TypeConstructorDescription { + case affectedFoundMessages(pts: Int32, ptsCount: Int32, offset: Int32, messages: [Int32]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .affectedFoundMessages(let pts, let ptsCount, let offset, let messages): + if boxed { + buffer.appendInt32(-275956116) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .affectedFoundMessages(let pts, let ptsCount, let offset, let messages): + return ("affectedFoundMessages", [("pts", pts as Any), ("ptsCount", ptsCount as Any), ("offset", offset as Any), ("messages", messages as Any)]) + } + } + + public static func parse_affectedFoundMessages(_ reader: BufferReader) -> AffectedFoundMessages? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.AffectedFoundMessages.affectedFoundMessages(pts: _1!, ptsCount: _2!, offset: _3!, messages: _4!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum AffectedHistory: TypeConstructorDescription { + case affectedHistory(pts: Int32, ptsCount: Int32, offset: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .affectedHistory(let pts, let ptsCount, let offset): + if boxed { + buffer.appendInt32(-1269012015) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .affectedHistory(let pts, let ptsCount, let offset): + return ("affectedHistory", [("pts", pts as Any), ("ptsCount", ptsCount as Any), ("offset", offset as Any)]) + } + } + + public static func parse_affectedHistory(_ reader: BufferReader) -> AffectedHistory? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.AffectedHistory.affectedHistory(pts: _1!, ptsCount: _2!, offset: _3!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum AffectedMessages: TypeConstructorDescription { + case affectedMessages(pts: Int32, ptsCount: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .affectedMessages(let pts, let ptsCount): + if boxed { + buffer.appendInt32(-2066640507) + } + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .affectedMessages(let pts, let ptsCount): + return ("affectedMessages", [("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + } + } + + public static func parse_affectedMessages(_ reader: BufferReader) -> AffectedMessages? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.AffectedMessages.affectedMessages(pts: _1!, ptsCount: _2!) + } + else { + return nil + } + } + + } +} public extension Api.messages { enum AllStickers: TypeConstructorDescription { case allStickers(hash: Int64, sets: [Api.StickerSet]) @@ -1258,145 +1396,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum ForumTopics: TypeConstructorDescription { - case forumTopics(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): - if boxed { - buffer.appendInt32(913709011) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(topics.count)) - for item in topics { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - serializeInt32(pts, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): - return ("forumTopics", [("flags", flags as Any), ("count", count as Any), ("topics", topics as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any), ("pts", pts as Any)]) - } - } - - public static func parse_forumTopics(_ reader: BufferReader) -> ForumTopics? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.ForumTopic]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) - } - var _4: [Api.Message]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _7: Int32? - _7 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.ForumTopics.forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum FoundStickerSets: TypeConstructorDescription { - case foundStickerSets(hash: Int64, sets: [Api.StickerSetCovered]) - case foundStickerSetsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .foundStickerSets(let hash, let sets): - if boxed { - buffer.appendInt32(-1963942446) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { - item.serialize(buffer, true) - } - break - case .foundStickerSetsNotModified: - if boxed { - buffer.appendInt32(223655517) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .foundStickerSets(let hash, let sets): - return ("foundStickerSets", [("hash", hash as Any), ("sets", sets as Any)]) - case .foundStickerSetsNotModified: - return ("foundStickerSetsNotModified", []) - } - } - - public static func parse_foundStickerSets(_ reader: BufferReader) -> FoundStickerSets? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.FoundStickerSets.foundStickerSets(hash: _1!, sets: _2!) - } - else { - return nil - } - } - public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? { - return Api.messages.FoundStickerSets.foundStickerSetsNotModified - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 421a977904..597af62556 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -1,3 +1,145 @@ +public extension Api.messages { + enum ForumTopics: TypeConstructorDescription { + case forumTopics(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): + if boxed { + buffer.appendInt32(913709011) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(topics.count)) + for item in topics { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + serializeInt32(pts, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .forumTopics(let flags, let count, let topics, let messages, let chats, let users, let pts): + return ("forumTopics", [("flags", flags as Any), ("count", count as Any), ("topics", topics as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any), ("pts", pts as Any)]) + } + } + + public static func parse_forumTopics(_ reader: BufferReader) -> ForumTopics? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.ForumTopic]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) + } + var _4: [Api.Message]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _7: Int32? + _7 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.messages.ForumTopics.forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum FoundStickerSets: TypeConstructorDescription { + case foundStickerSets(hash: Int64, sets: [Api.StickerSetCovered]) + case foundStickerSetsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .foundStickerSets(let hash, let sets): + if boxed { + buffer.appendInt32(-1963942446) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sets.count)) + for item in sets { + item.serialize(buffer, true) + } + break + case .foundStickerSetsNotModified: + if boxed { + buffer.appendInt32(223655517) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .foundStickerSets(let hash, let sets): + return ("foundStickerSets", [("hash", hash as Any), ("sets", sets as Any)]) + case .foundStickerSetsNotModified: + return ("foundStickerSetsNotModified", []) + } + } + + public static func parse_foundStickerSets(_ reader: BufferReader) -> FoundStickerSets? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.FoundStickerSets.foundStickerSets(hash: _1!, sets: _2!) + } + else { + return nil + } + } + public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? { + return Api.messages.FoundStickerSets.foundStickerSetsNotModified + } + + } +} public extension Api.messages { enum HighScores: TypeConstructorDescription { case highScores(scores: [Api.HighScore], users: [Api.User]) @@ -1368,61 +1510,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum Stickers: TypeConstructorDescription { - case stickers(hash: Int64, stickers: [Api.Document]) - case stickersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickers(let hash, let stickers): - if boxed { - buffer.appendInt32(816245886) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { - item.serialize(buffer, true) - } - break - case .stickersNotModified: - if boxed { - buffer.appendInt32(-244016606) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .stickers(let hash, let stickers): - return ("stickers", [("hash", hash as Any), ("stickers", stickers as Any)]) - case .stickersNotModified: - return ("stickersNotModified", []) - } - } - - public static func parse_stickers(_ reader: BufferReader) -> Stickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Stickers.stickers(hash: _1!, stickers: _2!) - } - else { - return nil - } - } - public static func parse_stickersNotModified(_ reader: BufferReader) -> Stickers? { - return Api.messages.Stickers.stickersNotModified - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 0e32580da6..bea7e93877 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -1,3 +1,61 @@ +public extension Api.messages { + enum Stickers: TypeConstructorDescription { + case stickers(hash: Int64, stickers: [Api.Document]) + case stickersNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickers(let hash, let stickers): + if boxed { + buffer.appendInt32(816245886) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { + item.serialize(buffer, true) + } + break + case .stickersNotModified: + if boxed { + buffer.appendInt32(-244016606) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stickers(let hash, let stickers): + return ("stickers", [("hash", hash as Any), ("stickers", stickers as Any)]) + case .stickersNotModified: + return ("stickersNotModified", []) + } + } + + public static func parse_stickers(_ reader: BufferReader) -> Stickers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.Stickers.stickers(hash: _1!, stickers: _2!) + } + else { + return nil + } + } + public static func parse_stickersNotModified(_ reader: BufferReader) -> Stickers? { + return Api.messages.Stickers.stickersNotModified + } + + } +} public extension Api.messages { enum TranscribedAudio: TypeConstructorDescription { case transcribedAudio(flags: Int32, transcriptionId: Int64, text: String) diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index f1461716a3..002737c88e 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -2911,6 +2911,111 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.communities { + static func checkCommunityInvite(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1753956947) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "communities.checkCommunityInvite", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.community.CommunityInvite? in + let reader = BufferReader(buffer) + var result: Api.community.CommunityInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.community.CommunityInvite + } + return result + }) + } +} +public extension Api.functions.communities { + static func deleteExportedInvite(community: Api.InputCommunity, slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-110213610) + community.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "communities.deleteExportedInvite", parameters: [("community", String(describing: community)), ("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.communities { + static func editExportedInvite(flags: Int32, community: Api.InputCommunity, slug: String, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(873155725) + serializeInt32(flags, buffer: buffer, boxed: false) + community.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "communities.editExportedInvite", parameters: [("flags", String(describing: flags)), ("community", String(describing: community)), ("slug", String(describing: slug)), ("title", String(describing: title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedCommunityInvite? in + let reader = BufferReader(buffer) + var result: Api.ExportedCommunityInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedCommunityInvite + } + return result + }) + } +} +public extension Api.functions.communities { + static func exportCommunityInvite(community: Api.InputCommunity, title: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1107192281) + community.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "communities.exportCommunityInvite", parameters: [("community", String(describing: community)), ("title", String(describing: title)), ("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.communities.ExportedCommunityInvite? in + let reader = BufferReader(buffer) + var result: Api.communities.ExportedCommunityInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.communities.ExportedCommunityInvite + } + return result + }) + } +} +public extension Api.functions.communities { + static func getExportedInvites(community: Api.InputCommunity) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1183359901) + community.serialize(buffer, true) + return (FunctionDescription(name: "communities.getExportedInvites", parameters: [("community", String(describing: community))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.communities.ExportedInvites? in + let reader = BufferReader(buffer) + var result: Api.communities.ExportedInvites? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.communities.ExportedInvites + } + return result + }) + } +} +public extension Api.functions.communities { + static func joinCommunityInvite(slug: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(82835751) + serializeString(slug, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "communities.joinCommunityInvite", parameters: [("slug", String(describing: slug)), ("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.contacts { static func acceptContact(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index fc20fb15d9..39e7c709cb 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -1015,6 +1015,7 @@ public extension Api { public extension Api { enum DialogFilter: TypeConstructorDescription { case dialogFilter(flags: Int32, id: Int32, title: String, emoticon: String?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer]) + case dialogFilterCommunity(flags: Int32, id: Int32, title: String, emoticon: String?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer]) case dialogFilterDefault public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -1043,6 +1044,25 @@ public extension Api { item.serialize(buffer, true) } break + case .dialogFilterCommunity(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers): + if boxed { + buffer.appendInt32(-665432009) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(pinnedPeers.count)) + for item in pinnedPeers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(includePeers.count)) + for item in includePeers { + item.serialize(buffer, true) + } + break case .dialogFilterDefault: if boxed { buffer.appendInt32(909284270) @@ -1056,6 +1076,8 @@ public extension Api { switch self { case .dialogFilter(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers, let excludePeers): return ("dialogFilter", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any), ("excludePeers", excludePeers as Any)]) + case .dialogFilterCommunity(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers): + return ("dialogFilterCommunity", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any)]) case .dialogFilterDefault: return ("dialogFilterDefault", []) } @@ -1096,6 +1118,36 @@ public extension Api { return nil } } + public static func parse_dialogFilterCommunity(_ reader: BufferReader) -> DialogFilter? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: String? + if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) } + var _5: [Api.InputPeer]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) + } + var _6: [Api.InputPeer]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 25) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.DialogFilter.dialogFilterCommunity(flags: _1!, id: _2!, title: _3!, emoticon: _4, pinnedPeers: _5!, includePeers: _6!) + } + else { + return nil + } + } public static func parse_dialogFilterDefault(_ reader: BufferReader) -> DialogFilter? { return Api.DialogFilter.dialogFilterDefault } diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index 1d327a9163..6631c3e671 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -1024,6 +1024,56 @@ public extension Api { } } +public extension Api { + enum ExportedCommunityInvite: TypeConstructorDescription { + case exportedCommunityInvite(title: String, url: String, peers: [Api.Peer]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedCommunityInvite(let title, let url, let peers): + if boxed { + buffer.appendInt32(-1350894801) + } + serializeString(title, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedCommunityInvite(let title, let url, let peers): + return ("exportedCommunityInvite", [("title", title as Any), ("url", url as Any), ("peers", peers as Any)]) + } + } + + public static func parse_exportedCommunityInvite(_ reader: BufferReader) -> ExportedCommunityInvite? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: [Api.Peer]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.ExportedCommunityInvite.exportedCommunityInvite(title: _1!, url: _2!, peers: _3!) + } + else { + return nil + } + } + + } +} public extension Api { enum ExportedContactToken: TypeConstructorDescription { case exportedContactToken(url: String, expires: Int32) @@ -1064,43 +1114,3 @@ public extension Api { } } -public extension Api { - enum ExportedMessageLink: TypeConstructorDescription { - case exportedMessageLink(link: String, html: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .exportedMessageLink(let link, let html): - if boxed { - buffer.appendInt32(1571494644) - } - serializeString(link, buffer: buffer, boxed: false) - serializeString(html, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .exportedMessageLink(let link, let html): - return ("exportedMessageLink", [("link", link as Any), ("html", html as Any)]) - } - } - - public static func parse_exportedMessageLink(_ reader: BufferReader) -> ExportedMessageLink? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ExportedMessageLink.exportedMessageLink(link: _1!, html: _2!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index 928e7ad570..8b9623cebe 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -1,3 +1,43 @@ +public extension Api { + enum ExportedMessageLink: TypeConstructorDescription { + case exportedMessageLink(link: String, html: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedMessageLink(let link, let html): + if boxed { + buffer.appendInt32(1571494644) + } + serializeString(link, buffer: buffer, boxed: false) + serializeString(html, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedMessageLink(let link, let html): + return ("exportedMessageLink", [("link", link as Any), ("html", html as Any)]) + } + } + + public static func parse_exportedMessageLink(_ reader: BufferReader) -> ExportedMessageLink? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.ExportedMessageLink.exportedMessageLink(link: _1!, html: _2!) + } + else { + return nil + } + } + + } +} public extension Api { enum FileHash: TypeConstructorDescription { case fileHash(offset: Int64, limit: Int32, hash: Buffer) diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index be9194440d..3751938295 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -532,6 +532,42 @@ public extension Api { } } +public extension Api { + enum InputCommunity: TypeConstructorDescription { + case inputCommunityDialogFilter(filterId: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputCommunityDialogFilter(let filterId): + if boxed { + buffer.appendInt32(450955169) + } + serializeInt32(filterId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputCommunityDialogFilter(let filterId): + return ("inputCommunityDialogFilter", [("filterId", filterId as Any)]) + } + } + + public static func parse_inputCommunityDialogFilter(_ reader: BufferReader) -> InputCommunity? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.InputCommunity.inputCommunityDialogFilter(filterId: _1!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputContact: TypeConstructorDescription { case inputPhoneContact(clientId: Int64, phone: String, firstName: String, lastName: String) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index c682dffef1..6a9cc74562 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 156 + return 158 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index 65765f33c6..8dd9b0a045 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -174,6 +174,7 @@ extension ChatListFilterIncludePeers { } public struct ChatListFilterData: Equatable, Hashable { + public var isShared: Bool public var categories: ChatListFilterPeerCategories public var excludeMuted: Bool public var excludeRead: Bool @@ -182,6 +183,7 @@ public struct ChatListFilterData: Equatable, Hashable { public var excludePeers: [PeerId] public init( + isShared: Bool, categories: ChatListFilterPeerCategories, excludeMuted: Bool, excludeRead: Bool, @@ -189,6 +191,7 @@ public struct ChatListFilterData: Equatable, Hashable { includePeers: ChatListFilterIncludePeers, excludePeers: [PeerId] ) { + self.isShared = isShared self.categories = categories self.excludeMuted = excludeMuted self.excludeRead = excludeRead @@ -246,6 +249,7 @@ public enum ChatListFilter: Codable, Equatable { let emoticon = try container.decodeIfPresent(String.self, forKey: "emoticon") let data = ChatListFilterData( + isShared: try container.decodeIfPresent(Bool.self, forKey: "isShared") ?? false, categories: ChatListFilterPeerCategories(rawValue: try container.decode(Int32.self, forKey: "categories")), excludeMuted: (try container.decode(Int32.self, forKey: "excludeMuted")) != 0, excludeRead: (try container.decode(Int32.self, forKey: "excludeRead")) != 0, @@ -275,6 +279,7 @@ public enum ChatListFilter: Codable, Equatable { try container.encode(title, forKey: "title") try container.encodeIfPresent(emoticon, forKey: "emoticon") + try container.encode(data.isShared, forKey: "isShared") try container.encode(data.categories.rawValue, forKey: "categories") try container.encode((data.excludeMuted ? 1 : 0) as Int32, forKey: "excludeMuted") try container.encode((data.excludeRead ? 1 : 0) as Int32, forKey: "excludeRead") @@ -297,6 +302,7 @@ extension ChatListFilter { title: title, emoticon: emoticon, data: ChatListFilterData( + isShared: false, categories: ChatListFilterPeerCategories(apiFlags: flags), excludeMuted: (flags & (1 << 11)) != 0, excludeRead: (flags & (1 << 12)) != 0, @@ -338,6 +344,43 @@ extension ChatListFilter { } ) ) + case let .dialogFilterCommunity(_, id, title, emoticon, pinnedPeers, includePeers): + self = .filter( + id: id, + title: title, + emoticon: emoticon, + data: ChatListFilterData( + isShared: true, + categories: [], + excludeMuted: false, + excludeRead: false, + excludeArchived: false, + includePeers: ChatListFilterIncludePeers(rawPeers: includePeers.compactMap { peer -> PeerId? in + switch peer { + case let .inputPeerUser(userId, _): + return PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .inputPeerChat(chatId): + return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) + case let .inputPeerChannel(channelId, _): + return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + default: + return nil + } + }, rawPinnedPeers: pinnedPeers.compactMap { peer -> PeerId? in + switch peer { + case let .inputPeerUser(userId, _): + return PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .inputPeerChat(chatId): + return PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) + case let .inputPeerChannel(channelId, _): + return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + default: + return nil + } + }), + excludePeers: [] + ) + ) } } @@ -455,6 +498,46 @@ private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, net } } + for peer in pinnedPeers { + var peerId: PeerId? + switch peer { + case let .inputPeerUser(userId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .inputPeerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) + case let .inputPeerChannel(channelId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + default: + break + } + if let peerId = peerId, !missingChatIds.contains(peerId) { + if transaction.getPeerChatListIndex(peerId) == nil { + missingChatIds.insert(peerId) + missingChats.append(peer) + } + } + } + case let .dialogFilterCommunity(_, _, _, _, pinnedPeers, includePeers): + for peer in pinnedPeers + includePeers { + var peerId: PeerId? + switch peer { + case let .inputPeerUser(userId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .inputPeerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) + case let .inputPeerChannel(channelId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + default: + break + } + if let peerId = peerId { + if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) { + missingPeerIds.insert(peerId) + missingPeers.append(peer) + } + } + } + for peer in pinnedPeers { var peerId: PeerId? switch peer { @@ -931,6 +1014,7 @@ public struct ChatListFeaturedFilter: Codable, Equatable { self.title = try container.decode(String.self, forKey: "title") self.description = try container.decode(String.self, forKey: "description") self.data = ChatListFilterData( + isShared: try container.decodeIfPresent(Bool.self, forKey: "isShared") ?? false, categories: ChatListFilterPeerCategories(rawValue: try container.decode(Int32.self, forKey: "categories")), excludeMuted: (try container.decode(Int32.self, forKey: "excludeMuted")) != 0, excludeRead: (try container.decode(Int32.self, forKey: "excludeRead")) != 0, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift new file mode 100644 index 0000000000..e7f395fcd3 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift @@ -0,0 +1,117 @@ +import Foundation +import SwiftSignalKit +import Postbox +import TelegramApi + +//communities.exportCommunityInvite#41fe69d9 community:InputCommunity title:string peers:Vector = communities.ExportedCommunityInvite; +//communities.exportedCommunityInvite#6b97a8ea filter:DialogFilter invite:ExportedCommunityInvite = communities.ExportedCommunityInvite; +//exportedCommunityInvite#af7afb2f title:string url:string peers:Vector = ExportedCommunityInvite; + +public enum ExportChatFolderError { + case generic +} + +public struct ExportedChatFolderLink: Equatable { + public var title: String + public var link: String + public var peerIds: [EnginePeer.Id] + + public init( + title: String, + link: String, + peerIds: [EnginePeer.Id] + ) { + self.title = title + self.link = link + self.peerIds = peerIds + } +} + +func _internal_exportChatFolder(account: Account, filterId: Int32, title: String, peerIds: [PeerId]) -> Signal { + return account.postbox.transaction { transaction -> [Api.InputPeer] in + return peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer) + } + |> castError(ExportChatFolderError.self) + |> mapToSignal { inputPeers -> Signal in + return account.network.request(Api.functions.communities.exportCommunityInvite(community: .inputCommunityDialogFilter(filterId: filterId), title: title, peers: inputPeers)) + |> mapError { _ -> ExportChatFolderError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> Signal in + switch result { + case let .exportedCommunityInvite(filter, invite): + let parsedFilter = ChatListFilter(apiFilter: filter) + + let _ = updateChatListFiltersState(transaction: transaction, { state in + var state = state + if let index = state.filters.firstIndex(where: { $0.id == filterId }) { + state.filters[index] = parsedFilter + } else { + state.filters.append(parsedFilter) + } + state.remoteFilters = state.filters + return state + }) + + switch invite { + case let .exportedCommunityInvite(title, url, peers): + return .single(ExportedChatFolderLink( + title: title, + link: url, + peerIds: peers.map(\.peerId) + )) + } + } + } + |> castError(ExportChatFolderError.self) + |> switchToLatest + } + } +} + +func _internal_getExportedChatLinks(account: Account, id: Int32) -> Signal<[ExportedChatFolderLink], NoError> { + return account.network.request(Api.functions.communities.getExportedInvites(community: .inputCommunityDialogFilter(filterId: id))) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal<[ExportedChatFolderLink], NoError> in + guard let result = result else { + return .single([]) + } + return account.postbox.transaction { transaction -> [ExportedChatFolderLink] in + switch result { + case let .exportedInvites(invites, chats, users): + var peers: [Peer] = [] + var peerPresences: [PeerId: Api.User] = [:] + + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + peerPresences[telegramUser.id] = user + } + for chat in chats { + if let peer = parseTelegramGroupOrChannel(chat: chat) { + peers.append(peer) + } + } + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) + + var result: [ExportedChatFolderLink] = [] + for invite in invites { + switch invite { + case let .exportedCommunityInvite(title, url, peers): + result.append(ExportedChatFolderLink(title: title, link: url, peerIds: peers.map(\.peerId))) + } + } + + return result + } + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 963e3b98b3..ff6052cfed 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1025,6 +1025,14 @@ public extension TelegramEngine { |> ignoreValues } } + + public func exportChatFolder(filterId: Int32, title: String, peerIds: [PeerId]) -> Signal { + return _internal_exportChatFolder(account: self.account, filterId: filterId, title: title, peerIds: peerIds) + } + + public func getExportedChatLinks(id: Int32) -> Signal<[ExportedChatFolderLink], NoError> { + return _internal_getExportedChatLinks(account: self.account, id: id) + } } }