diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD index e4748b0bc4..733cb8688e 100644 --- a/submodules/ContactListUI/BUILD +++ b/submodules/ContactListUI/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", diff --git a/submodules/ContactListUI/Sources/ContactAddItem.swift b/submodules/ContactListUI/Sources/ContactAddItem.swift index 1088e2dd1c..ec494117f8 100644 --- a/submodules/ContactListUI/Sources/ContactAddItem.swift +++ b/submodules/ContactListUI/Sources/ContactAddItem.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import Display import SwiftSignalKit import TelegramCore diff --git a/submodules/ContactListUI/Sources/ContactContextMenus.swift b/submodules/ContactListUI/Sources/ContactContextMenus.swift index 27c51714c2..6db9ce42b8 100644 --- a/submodules/ContactListUI/Sources/ContactContextMenus.swift +++ b/submodules/ContactListUI/Sources/ContactContextMenus.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import ContextUI import AccountContext -import Postbox import TelegramCore import Display import AlertUI @@ -11,7 +10,7 @@ import PresentationDataUtils import OverlayStatusController import LocalizedPeerData -func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsController: ContactsController?) -> Signal<[ContextMenuItem], NoError> { +func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, contactsController: ContactsController?) -> Signal<[ContextMenuItem], NoError> { let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings return context.account.postbox.transaction { [weak contactsController] transaction -> [ContextMenuItem] in var items: [ContextMenuItem] = [] @@ -32,9 +31,9 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo if canStartSecretChat { items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_StartSecretChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = (context.account.postbox.transaction { transaction -> PeerId? in + let _ = (context.account.postbox.transaction { transaction -> EnginePeer.Id? in let filteredPeerIds = Array(transaction.getAssociatedPeerIds(peerId)).filter { $0.namespace == Namespaces.Peer.SecretChat } - var activeIndices: [ChatListIndex] = [] + var activeIndices: [EngineChatList.Item.Index] = [] for associatedId in filteredPeerIds { if let state = (transaction.getPeer(associatedId) as? TelegramSecretChat)?.embeddedState { switch state { diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 9531a1ad2a..7450257dcc 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -101,11 +100,11 @@ private final class ContactListNodeInteraction { fileprivate let authorize: () -> Void fileprivate let suppressWarning: () -> Void fileprivate let openPeer: (ContactListPeer, ContactListAction) -> Void - fileprivate let contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? + fileprivate let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? let itemHighlighting = ContactItemHighlighting() - init(activateSearch: @escaping () -> Void, openSortMenu: @escaping () -> Void, authorize: @escaping () -> Void, suppressWarning: @escaping () -> Void, openPeer: @escaping (ContactListPeer, ContactListAction) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) { + init(activateSearch: @escaping () -> Void, openSortMenu: @escaping () -> Void, authorize: @escaping () -> Void, suppressWarning: @escaping () -> Void, openPeer: @escaping (ContactListPeer, ContactListAction) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?) { self.activateSearch = activateSearch self.openSortMenu = openSortMenu self.authorize = authorize @@ -127,7 +126,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { case permissionInfo(PresentationTheme, String, String, Bool) case permissionEnable(PresentationTheme, String) case option(Int, ContactListAdditionalOption, ListViewItemHeader?, PresentationTheme, PresentationStrings) - case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool, Bool) + case peer(Int, ContactListPeer, EnginePeer.Presence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool, Bool) var stableId: ContactListNodeEntryId { switch self { @@ -186,8 +185,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { status = .addressName("") } else { if let _ = peer as? TelegramUser { - let presence = presence ?? TelegramUserPresence(status: .none, lastActivity: 0) - status = .presence(EnginePeer.Presence(presence), dateTimeFormat) + status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat) } else if let group = peer as? TelegramGroup { status = .custom(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), multiline: false) } else if let channel = peer as? TelegramChannel { @@ -222,7 +220,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { switch itemPeer { case let .peer(peer, _): if let peer = peer { - contextAction(peer._asPeer(), node, gesture) + contextAction(peer, node, gesture) } case .deviceContact: break @@ -287,7 +285,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { return false } if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { - if !lhsPresence.isEqual(to: rhsPresence) { + if lhsPresence != rhsPresence { return false } } else if (lhsPresence != nil) != (rhsPresence != nil) { @@ -372,73 +370,73 @@ private enum ContactListNodeEntry: Comparable, Identifiable { } } -private extension PeerIndexNameRepresentation { - func isLessThan(other: PeerIndexNameRepresentation, ordering: PresentationPersonNameOrder) -> ComparisonResult { +private extension EnginePeer.IndexName { + func isLessThan(other: EnginePeer.IndexName, ordering: PresentationPersonNameOrder) -> ComparisonResult { switch self { - case let .title(lhsTitle, _): - let rhsString: String - switch other { - case let .title(title, _): - rhsString = title - case let .personName(first, last, _, _): - switch ordering { - case .firstLast: - if first.isEmpty { - rhsString = last - } else { - rhsString = first + last - } - case .lastFirst: - if last.isEmpty { - rhsString = first - } else { - rhsString = last + first - } - } - } - return lhsTitle.caseInsensitiveCompare(rhsString) - case let .personName(lhsFirst, lhsLast, _, _): - let lhsString: String + case let .title(lhsTitle, _): + let rhsString: String + switch other { + case let .title(title, _): + rhsString = title + case let .personName(first, last, _, _): switch ordering { - case .firstLast: - if lhsFirst.isEmpty { - lhsString = lhsLast - } else { - lhsString = lhsFirst + lhsLast - } - case .lastFirst: - if lhsLast.isEmpty { - lhsString = lhsFirst - } else { - lhsString = lhsLast + lhsFirst - } + case .firstLast: + if first.isEmpty { + rhsString = last + } else { + rhsString = first + last + } + case .lastFirst: + if last.isEmpty { + rhsString = first + } else { + rhsString = last + first + } } - let rhsString: String - switch other { - case let .title(title, _): - rhsString = title - case let .personName(first, last, _, _): - switch ordering { - case .firstLast: - if first.isEmpty { - rhsString = last - } else { - rhsString = first + last - } - case .lastFirst: - if last.isEmpty { - rhsString = first - } else { - rhsString = last + first - } - } + } + return lhsTitle.caseInsensitiveCompare(rhsString) + case let .personName(lhsFirst, lhsLast, _, _): + let lhsString: String + switch ordering { + case .firstLast: + if lhsFirst.isEmpty { + lhsString = lhsLast + } else { + lhsString = lhsFirst + lhsLast } - return lhsString.caseInsensitiveCompare(rhsString) + case .lastFirst: + if lhsLast.isEmpty { + lhsString = lhsFirst + } else { + lhsString = lhsLast + lhsFirst + } + } + let rhsString: String + switch other { + case let .title(title, _): + rhsString = title + case let .personName(first, last, _, _): + switch ordering { + case .firstLast: + if first.isEmpty { + rhsString = last + } else { + rhsString = first + last + } + case .lastFirst: + if last.isEmpty { + rhsString = first + } else { + rhsString = last + first + } + } + } + return lhsString.caseInsensitiveCompare(rhsString) } } } -private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds:Set, authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool) -> [ContactListNodeEntry] { +private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set, authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool) -> [ContactListNodeEntry] { var entries: [ContactListNodeEntry] = [] var commonHeader: ListViewItemHeader? @@ -480,7 +478,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] if case let .peer(lhsPeer, _, _) = lhs, case let .peer(rhsPeer, _, _) = rhs { let lhsPresence = presences[lhsPeer.id] let rhsPresence = presences[rhsPeer.id] - if let lhsPresence = lhsPresence as? TelegramUserPresence, let rhsPresence = rhsPresence as? TelegramUserPresence { + if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { if lhsPresence.status < rhsPresence.status { return false } else if lhsPresence.status > rhsPresence.status { @@ -503,7 +501,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] } case let .natural(options, _): let sortedPeers = peers.sorted(by: { lhs, rhs in - let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: sortOrder) + let result = EnginePeer.IndexName(lhs.indexName).isLessThan(other: EnginePeer.IndexName(rhs.indexName), ordering: sortOrder) if result == .orderedSame { if case let .peer(lhsPeer, _, _) = lhs, case let .peer(rhsPeer, _, _) = rhs { return lhsPeer.id < rhsPeer.id @@ -606,7 +604,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] let selection: ContactsPeerItemSelection = .selectable(selected: selectionState.selectedPeerIndices[peer.id] != nil) - var presence: PeerPresence? + var presence: EnginePeer.Presence? if case let .peer(peer, _, _) = peer { presence = presences[peer.id] } @@ -643,7 +641,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] default: header = headers[peer.id] } - var presence: PeerPresence? + var presence: EnginePeer.Presence? if case let .peer(peer, _, _) = peer { presence = presences[peer.id] } @@ -798,7 +796,7 @@ public final class ContactListNode: ASDisplayNode { } private var didSetReady = false - private let contactPeersViewPromise = Promise() + private let contactPeersViewPromise = Promise<(EngineContactList, EnginePeer?)>() private let selectionStatePromise = Promise(nil) private var selectionStateValue: ContactListNodeGroupSelectionState? { @@ -843,11 +841,23 @@ public final class ContactListNode: ASDisplayNode { if value != self.enableUpdatesValue { self.enableUpdatesValue = value if value { - self.contactPeersViewPromise.set(self.context.account.postbox.contactPeersView(accountPeerId: self.context.account.peerId, includePresences: true) |> mapToThrottled { next -> Signal in - return .single(next) |> then(.complete() |> delay(5.0, queue: Queue.concurrentDefaultQueue())) + self.contactPeersViewPromise.set(self.context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: true), + TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.engine.account.peerId) + ) + |> mapToThrottled { next -> Signal<(EngineContactList, EnginePeer?), NoError> in + return .single(next) + |> then( + .complete() + |> delay(5.0, queue: Queue.concurrentDefaultQueue()) + ) }) } else { - self.contactPeersViewPromise.set(self.context.account.postbox.contactPeersView(accountPeerId: self.context.account.peerId, includePresences: true) |> take(1)) + self.contactPeersViewPromise.set(self.context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: true), + TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.engine.account.peerId) + ) + |> take(1)) } } } @@ -861,7 +871,7 @@ public final class ContactListNode: ASDisplayNode { public var openPeer: ((ContactListPeer, ContactListAction) -> Void)? public var openPrivacyPolicy: (() -> Void)? public var suppressPermissionWarning: (() -> Void)? - private let contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? + private let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? private let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil) private let disposable = MetaDisposable() @@ -875,7 +885,7 @@ public final class ContactListNode: ASDisplayNode { public var multipleSelection = false - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, presentation: Signal, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, presentation: Signal, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) { self.context = context self.filters = filters self.displayPermissionPlaceholder = displayPermissionPlaceholder @@ -1037,11 +1047,11 @@ public final class ContactListNode: ASDisplayNode { if case let .search(query, searchChatList, searchDeviceContacts, searchGroups, searchChannels, globalSearch) = presentation { return query |> mapToSignal { query in - let foundLocalContacts: Signal<([FoundPeer], [PeerId: PeerPresence]), NoError> + let foundLocalContacts: Signal<([FoundPeer], [EnginePeer.Id: EnginePeer.Presence]), NoError> if searchChatList { let foundChatListPeers = context.account.postbox.searchPeers(query: query.lowercased()) foundLocalContacts = foundChatListPeers - |> mapToSignal { peers -> Signal<([FoundPeer], [PeerId: PeerPresence]), NoError> in + |> mapToSignal { peers -> Signal<([FoundPeer], [EnginePeer.Id: EnginePeer.Presence]), NoError> in var resultPeers: [FoundPeer] = [] for peer in peers { @@ -1067,12 +1077,12 @@ public final class ContactListNode: ASDisplayNode { resultPeers.append(FoundPeer(peer: mainPeer, subscribers: nil)) } } - return context.account.postbox.transaction { transaction -> ([FoundPeer], [PeerId: PeerPresence]) in - var resultPresences: [PeerId: PeerPresence] = [:] + return context.account.postbox.transaction { transaction -> ([FoundPeer], [EnginePeer.Id: EnginePeer.Presence]) in + var resultPresences: [EnginePeer.Id: EnginePeer.Presence] = [:] var mappedPeers: [FoundPeer] = [] for peer in resultPeers { if let presence = transaction.getPeerPresence(peerId: peer.peer.id) { - resultPresences[peer.peer.id] = presence + resultPresences[peer.peer.id] = EnginePeer.Presence(presence) } if let _ = peer.peer as? TelegramChannel { var subscribers: Int32? @@ -1088,9 +1098,9 @@ public final class ContactListNode: ASDisplayNode { } } } else { - foundLocalContacts = context.account.postbox.searchContacts(query: query.lowercased()) - |> map { peers, presences -> ([FoundPeer], [PeerId: PeerPresence]) in - return (peers.map({ FoundPeer(peer: $0, subscribers: nil) }), presences) + foundLocalContacts = context.engine.contacts.searchContacts(query: query.lowercased()) + |> map { peers, presences -> ([FoundPeer], [EnginePeer.Id: EnginePeer.Presence]) in + return (peers.map({ FoundPeer(peer: $0._asPeer(), subscribers: nil) }), presences) } } var foundRemoteContacts: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], [])) @@ -1102,7 +1112,7 @@ public final class ContactListNode: ASDisplayNode { |> delay(0.2, queue: Queue.concurrentDefaultQueue()) ) } - let foundDeviceContacts: Signal<[DeviceContactStableId: (DeviceContactBasicData, PeerId?)], NoError> + let foundDeviceContacts: Signal<[DeviceContactStableId: (DeviceContactBasicData, EnginePeer.Id?)], NoError> if searchDeviceContacts { foundDeviceContacts = context.sharedContext.contactDataManager?.search(query: query) ?? .single([:]) } else { @@ -1115,8 +1125,8 @@ public final class ContactListNode: ASDisplayNode { return combineLatest(accountPeer, foundLocalContacts, foundRemoteContacts, foundDeviceContacts, selectionStateSignal, presentationDataPromise.get()) |> mapToQueue { accountPeer, localPeersAndStatuses, remotePeers, deviceContacts, selectionState, presentationData -> Signal in let signal = deferred { () -> Signal in - var existingPeerIds = Set() - var disabledPeerIds = Set() + var existingPeerIds = Set() + var disabledPeerIds = Set() var existingNormalizedPhoneNumbers = Set() var excludeSelf = false @@ -1249,25 +1259,25 @@ public final class ContactListNode: ASDisplayNode { } } } else { - let chatListSignal: Signal<[(Peer, Int32)], NoError> + let chatListSignal: Signal<[(EnginePeer, Int32)], NoError> if includeChatList { chatListSignal = self.context.account.viewTracker.tailChatListView(groupId: .root, count: 100) |> take(1) - |> mapToSignal { view, _ -> Signal<[(Peer, Int32)], NoError> in - return context.account.postbox.transaction { transaction -> [(Peer, Int32)] in - var peers: [(Peer, Int32)] = [] + |> mapToSignal { view, _ -> Signal<[(EnginePeer, Int32)], NoError> in + return context.account.postbox.transaction { transaction -> [(EnginePeer, Int32)] in + var peers: [(EnginePeer, Int32)] = [] for entry in view.entries { switch entry { case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _): if let peer = renderedPeer.peer { if peer is TelegramGroup { - peers.append((peer, 0)) + peers.append((EnginePeer(peer), 0)) } else if let channel = peer as? TelegramChannel, case .group = channel.info { var memberCount: Int32 = 0 if let cachedData = transaction.getPeerCachedData(peerId: peer.id) as? CachedChannelData { memberCount = cachedData.participantsSummary.memberCount ?? 0 } - peers.append((peer, memberCount)) + peers.append((EnginePeer(peer), memberCount)) } } default: @@ -1284,12 +1294,12 @@ public final class ContactListNode: ASDisplayNode { return (combineLatest(self.contactPeersViewPromise.get(), chatListSignal, selectionStateSignal, presentationDataPromise.get(), contactsAuthorization.get(), contactsWarningSuppressed.get()) |> mapToQueue { view, chatListPeers, selectionState, presentationData, authorizationStatus, warningSuppressed -> Signal in let signal = deferred { () -> Signal in - var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false, participantCount: nil) }) + var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) }) for (peer, memberCount) in chatListPeers { - peers.append(.peer(peer: peer, isGlobal: false, participantCount: memberCount)) + peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount)) } - var existingPeerIds = Set() - var disabledPeerIds = Set() + var existingPeerIds = Set() + var disabledPeerIds = Set() for filter in filters { switch filter { case .excludeSelf: @@ -1314,7 +1324,7 @@ public final class ContactListNode: ASDisplayNode { if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty { isEmpty = true } - let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: peers, presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons) + let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons) let previous = previousEntries.swap(entries) let previousSelection = previousSelectionState.swap(selectionState) diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 3b94c6845e..fafccdc58f 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData @@ -71,7 +70,7 @@ public class ContactsController: ViewController { } private var validLayout: ContainerViewLayout? - private let index: PeerNameIndex = .lastNameFirst + private let index: PresentationPersonNameOrder = .lastFirst private var _ready = Promise() override public var ready: Promise { diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index ed873ef808..c049b31264 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -2,7 +2,6 @@ import Display import UIKit import AsyncDisplayKit import UIKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -87,7 +86,7 @@ final class ContactsControllerNode: ASDisplayNode { } } - var contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? + var contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? self.contactListNode = ContactListNode(context: context, presentation: presentation, displaySortOptions: true, contextAction: { peer, node, gesture in contextAction?(peer, node, gesture) @@ -169,7 +168,7 @@ final class ContactsControllerNode: ASDisplayNode { self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size) } - private func contextAction(peer: Peer, node: ASDisplayNode, gesture: ContextGesture?) { + private func contextAction(peer: EnginePeer, node: ASDisplayNode, gesture: ContextGesture?) { guard let contactsController = self.controller else { return } diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index 39361248d2..641c8d133a 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -54,7 +53,7 @@ private enum ContactListSearchEntryId: Hashable { private enum ContactListSearchEntry: Comparable, Identifiable { case addContact(PresentationTheme, PresentationStrings, String) - case peer(Int, PresentationTheme, PresentationStrings, ContactListPeer, PeerPresence?, ContactListSearchGroup, Bool) + case peer(Int, PresentationTheme, PresentationStrings, ContactListPeer, EnginePeer.Presence?, ContactListSearchGroup, Bool) var stableId: ContactListSearchEntryId { switch self { @@ -89,7 +88,7 @@ private enum ContactListSearchEntry: Comparable, Identifiable { return false } if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { - if !lhsPresence.isEqual(to: rhsPresence) { + if lhsPresence != rhsPresence { return false } } else if (lhsPresence != nil) != (rhsPresence != nil) { @@ -122,7 +121,7 @@ private enum ContactListSearchEntry: Comparable, Identifiable { } } - func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { + func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { switch self { case let .addContact(theme, strings, phoneNumber): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -135,7 +134,7 @@ private enum ContactListSearchEntry: Comparable, Identifiable { case .contacts: header = ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil) if let presence = presence { - status = .presence(EnginePeer.Presence(presence), timeFormat) + status = .presence(presence, timeFormat) } else { status = .none } @@ -150,12 +149,12 @@ private enum ContactListSearchEntry: Comparable, Identifiable { header = ChatListSearchItemHeader(type: .deviceContacts, theme: theme, strings: strings, actionTitle: nil, action: nil) status = .none } - var nativePeer: Peer? + var nativePeer: EnginePeer? let peerItem: ContactsPeerItemPeer switch peer { case let .peer(peer, _, _): peerItem = .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)) - nativePeer = peer + nativePeer = EnginePeer(peer) case let .deviceContact(stableId, contact): peerItem = .deviceContact(stableId: stableId, contact: contact) } @@ -181,7 +180,7 @@ struct ContactListSearchContainerTransition { let query: String } -private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, emptyResults: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) -> ContactListSearchContainerTransition { +private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, emptyResults: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?) -> ContactListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -207,7 +206,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo private let context: AccountContext private let addContact: ((String) -> Void)? private let openPeer: (ContactListPeer) -> Void - private let contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? + private let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? private let dimNode: ASDisplayNode public let listNode: ListView @@ -230,7 +229,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo return true } - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?) -> Void)?) { self.context = context self.addContact = addContact self.openPeer = openPeer @@ -289,9 +288,9 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo let searchItems = self.searchQuery.get() |> mapToSignal { query -> Signal<([ContactListSearchEntry]?, String), NoError> in if let query = query, !query.isEmpty { - let foundLocalContacts: Signal<([Peer], [PeerId: PeerPresence]), NoError> + let foundLocalContacts: Signal<([EnginePeer], [EnginePeer.Id: EnginePeer.Presence]), NoError> if categories.contains(.cloudContacts) { - foundLocalContacts = context.account.postbox.searchContacts(query: query.lowercased()) + foundLocalContacts = context.engine.contacts.searchContacts(query: query.lowercased()) } else { foundLocalContacts = .single(([], [:])) } @@ -307,7 +306,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo foundRemoteContacts = .single(([], [])) } let searchDeviceContacts = categories.contains(.deviceContacts) - let foundDeviceContacts: Signal<[DeviceContactStableId: (DeviceContactBasicData, PeerId?)]?, NoError> + let foundDeviceContacts: Signal<[DeviceContactStableId: (DeviceContactBasicData, EnginePeer.Id?)]?, NoError> if searchDeviceContacts, let contactDataManager = context.sharedContext.contactDataManager { foundDeviceContacts = contactDataManager.search(query: query) |> map(Optional.init) @@ -321,8 +320,8 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo let _ = previousFoundRemoteContacts.swap(remotePeers) var entries: [ContactListSearchEntry] = [] - var existingPeerIds = Set() - var disabledPeerIds = Set() + var existingPeerIds = Set() + var disabledPeerIds = Set() for filter in filters { switch filter { case .excludeSelf: @@ -342,10 +341,10 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo existingPeerIds.insert(peer.id) var enabled = true if onlyWriteable { - enabled = canSendMessagesToPeer(peer) + enabled = canSendMessagesToPeer(peer._asPeer()) } - entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .peer(peer: peer, isGlobal: false, participantCount: nil), localPeersAndPresences.1[peer.id], .contacts, enabled)) - if searchDeviceContacts, let user = peer as? TelegramUser, let phone = user.phone { + entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), localPeersAndPresences.1[peer.id], .contacts, enabled)) + if searchDeviceContacts, case let .user(user) = peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } index += 1 diff --git a/submodules/ContactListUI/Sources/InviteContactsController.swift b/submodules/ContactListUI/Sources/InviteContactsController.swift index 7f52577b8d..24e19d6efd 100644 --- a/submodules/ContactListUI/Sources/InviteContactsController.swift +++ b/submodules/ContactListUI/Sources/InviteContactsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import MessageUI diff --git a/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift b/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift index 8d64b61cff..d8facc8d8d 100644 --- a/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift @@ -2,7 +2,6 @@ import Display import UIKit import AsyncDisplayKit import UIKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -57,8 +56,8 @@ private enum InviteContactsEntry: Comparable, Identifiable { } else { status = .none } - let peer = TelegramUser(id: PeerId(namespace: .max, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: contact.firstName, lastName: contact.lastName, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)), status: status, enabled: true, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in + let peer: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: contact.firstName, lastName: contact.lastName, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])) + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: status, enabled: true, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in interaction.toggleContact(id) }) } @@ -183,7 +182,7 @@ struct InviteContactsGroupSelectionState: Equatable { } } -private func inviteContactsEntries(accountPeer: Peer?, sortedContacts: [(DeviceContactStableId, DeviceContactBasicData, Int32)]?, selectionState: InviteContactsGroupSelectionState, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: InviteContactsInteraction) -> [InviteContactsEntry] { +private func inviteContactsEntries(accountPeer: EnginePeer?, sortedContacts: [(DeviceContactStableId, DeviceContactBasicData, Int32)]?, selectionState: InviteContactsGroupSelectionState, theme: PresentationTheme, strings: PresentationStrings, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: InviteContactsInteraction) -> [InviteContactsEntry] { var entries: [InviteContactsEntry] = [] entries.append(.option(0, ContactListAdditionalOption(title: strings.Contacts_ShareTelegram, icon: .generic(UIImage(bundleImageName: "Contact List/InviteActionIcon")!), action: { @@ -328,7 +327,6 @@ final class InviteContactsControllerNode: ASDisplayNode { } }) - let account = self.context.account let selectionStateSignal = self.selectionStatePromise.get() let transition: Signal let presentationDataPromise = self.presentationDataPromise @@ -342,12 +340,14 @@ final class InviteContactsControllerNode: ASDisplayNode { self?.requestShareTelegram?() }) - let existingNumbers: Signal<(Set, Set), NoError> = account.postbox.contactPeersView(accountPeerId: nil, includePresences: false) - |> map { view -> (Set, Set) in + let existingNumbers: Signal<(Set, Set), NoError> = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: false) + ) + |> map { view -> (Set, Set) in var existingNumbers = Set() - var existingPeerIds = Set() + var existingPeerIds = Set() for peer in view.peers { - if let peer = peer as? TelegramUser, let phone = peer.phone { + if case let .user(peer) = peer, let phone = peer.phone { existingNumbers.insert(formatPhoneNumber(phone)) } existingPeerIds.insert(peer.id) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index e55cb6ead0..a160580a91 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -242,7 +242,12 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { let additionalDisposable = MetaDisposable() if peerId.namespace == Namespaces.Peer.CloudGroup { - let disposable = combineLatest(queue: Queue.mainQueue(), context.account.postbox.peerView(id: peerId), context.account.postbox.contactPeersView(accountPeerId: context.account.peerId, includePresences: true)).start(next: { [weak self] peerView, contactsView in + let disposable = combineLatest(queue: Queue.mainQueue(), + context.account.postbox.peerView(id: peerId), + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: true) + ) + ).start(next: { [weak self] peerView, contactsView in guard let strongSelf = self else { return } @@ -405,7 +410,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } }) { for peer in contactsView.peers { - entries.append(ChannelMembersSearchEntry.contact(index, peer, contactsView.peerPresences[peer.id] as? TelegramUserPresence)) + entries.append(ChannelMembersSearchEntry.contact(index, peer._asPeer(), contactsView.presences[peer.id]?._asPresence())) index += 1 } } @@ -425,7 +430,9 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { additionalDisposable.set((combineLatest(queue: .mainQueue(), membersState.get(), context.account.postbox.peerView(id: peerId), - context.account.postbox.contactPeersView(accountPeerId: context.account.peerId, includePresences: true) + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: true) + ) ).start(next: { [weak self] state, peerView, contactsView in guard let strongSelf = self else { return @@ -554,7 +561,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } }) { for peer in contactsView.peers { - entries.append(ChannelMembersSearchEntry.contact(index, peer, contactsView.peerPresences[peer.id] as? TelegramUserPresence)) + entries.append(ChannelMembersSearchEntry.contact(index, peer._asPeer(), contactsView.presences[peer.id]?._asPresence())) index += 1 } } diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 22a49b17b2..d61735ae9c 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -1361,15 +1361,16 @@ private func addContactToExisting(context: AccountContext, parentController: Vie guard let contactData = contactData else { return } - let _ = (context.account.postbox.contactPeersView(accountPeerId: nil, includePresences: false) - |> take(1) + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: false) + ) |> deliverOnMainQueue).start(next: { view in let phones = Set(contactData.basicData.phoneNumbers.map { return formatPhoneNumber($0.value) }) - var foundPeer: Peer? + var foundPeer: EnginePeer? for peer in view.peers { - if let user = peer as? TelegramUser, let phone = user.phone { + if case let .user(user) = peer, let phone = user.phone { let phone = formatPhoneNumber(phone) if phones.contains(phone) { foundPeer = peer @@ -1377,7 +1378,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie } } } - completion(foundPeer, stableId, contactData) + completion(foundPeer?._asPeer(), stableId, contactData) }) }) } diff --git a/submodules/Postbox/Sources/ContactPeersView.swift b/submodules/Postbox/Sources/ContactPeersView.swift index dc9ea70ec3..341646e179 100644 --- a/submodules/Postbox/Sources/ContactPeersView.swift +++ b/submodules/Postbox/Sources/ContactPeersView.swift @@ -1,33 +1,47 @@ import Foundation -final class MutableContactPeersView { +final class MutableContactPeersView: MutablePostboxView { fileprivate var peers: [PeerId: Peer] fileprivate var peerPresences: [PeerId: PeerPresence] fileprivate var peerIds: Set fileprivate var accountPeer: Peer? private let includePresences: Bool - init(peers: [PeerId: Peer], peerPresences: [PeerId: PeerPresence], accountPeer: Peer?, includePresences: Bool) { + init(postbox: PostboxImpl, accountPeerId: PeerId?, includePresences: Bool) { + var peers: [PeerId: Peer] = [:] + var peerPresences: [PeerId: PeerPresence] = [:] + + for peerId in postbox.contactsTable.get() { + if let peer = postbox.peerTable.get(peerId) { + peers[peerId] = peer + } + if includePresences { + if let presence = postbox.peerPresenceTable.get(peerId) { + peerPresences[peerId] = presence + } + } + } + self.peers = peers self.peerIds = Set(peers.map { $0.0 }) self.peerPresences = peerPresences - self.accountPeer = accountPeer + self.accountPeer = accountPeerId.flatMap(postbox.peerTable.get) self.includePresences = includePresences } - - func replay(postbox: PostboxImpl, replacePeerIds: Set?, updatedPeerPresences: [PeerId: PeerPresence]) -> Bool { + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { var updated = false - if let replacePeerIds = replacePeerIds { + if let replacePeerIds = transaction.replaceContactPeerIds { let removedPeerIds = self.peerIds.subtracting(replacePeerIds) let addedPeerIds = replacePeerIds.subtracting(self.peerIds) - + self.peerIds = replacePeerIds - + for peerId in removedPeerIds { let _ = self.peers.removeValue(forKey: peerId) let _ = self.peerPresences.removeValue(forKey: peerId) } - + for peerId in addedPeerIds { if let peer = postbox.peerTable.get(peerId) { self.peers[peerId] = peer @@ -38,26 +52,30 @@ final class MutableContactPeersView { } } } - + if !removedPeerIds.isEmpty || !addedPeerIds.isEmpty { updated = true } } - - if self.includePresences, !updatedPeerPresences.isEmpty { + + if self.includePresences, !transaction.currentUpdatedPeerPresences.isEmpty { for peerId in self.peerIds { - if let presence = updatedPeerPresences[peerId] { + if let presence = transaction.currentUpdatedPeerPresences[peerId] { updated = true self.peerPresences[peerId] = presence } } } - + return updated } + + func immutableView() -> PostboxView { + return ContactPeersView(self) + } } -public final class ContactPeersView { +public final class ContactPeersView: PostboxView { public let peers: [Peer] public let peerPresences: [PeerId: PeerPresence] public let accountPeer: Peer? diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 1f0f2756bd..2378e693db 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -2901,42 +2901,6 @@ final class PostboxImpl { } } - public func contactPeersView(accountPeerId: PeerId?, includePresences: Bool) -> Signal { - return self.transactionSignal { subscriber, transaction in - var peers: [PeerId: Peer] = [:] - var peerPresences: [PeerId: PeerPresence] = [:] - - for peerId in self.contactsTable.get() { - if let peer = self.peerTable.get(peerId) { - peers[peerId] = peer - } - if includePresences { - if let presence = self.peerPresenceTable.get(peerId) { - peerPresences[peerId] = presence - } - } - } - - let view = MutableContactPeersView(peers: peers, peerPresences: peerPresences, accountPeer: accountPeerId.flatMap(self.peerTable.get), includePresences: includePresences) - let (index, signal) = self.viewTracker.addContactPeersView(view) - - subscriber.putNext(ContactPeersView(view)) - let disposable = signal.start(next: { next in - subscriber.putNext(next) - }) - - return ActionDisposable { - [weak self] in - disposable.dispose() - if let strongSelf = self { - strongSelf.queue.async { - strongSelf.viewTracker.removeContactPeersView(index) - } - } - } - } - } - public func searchContacts(query: String) -> Signal<([Peer], [PeerId: PeerPresence]), NoError> { return self.transaction { transaction -> Signal<([Peer], [PeerId: PeerPresence]), NoError> in let (_, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable) @@ -3902,18 +3866,6 @@ public class Postbox { } } - public func contactPeersView(accountPeerId: PeerId?, includePresences: Bool) -> Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - disposable.set(impl.contactPeersView(accountPeerId: accountPeerId, includePresences: includePresences).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)) - } - - return disposable - } - } - public func searchContacts(query: String) -> Signal<([Peer], [PeerId: PeerPresence]), NoError> { return Signal { subscriber in let disposable = MetaDisposable() diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index 9511477fe1..870e6c0ace 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -15,7 +15,6 @@ final class ViewTracker { private var chatListViews = Bag<(MutableChatListView, ValuePipe<(ChatListView, ViewUpdateType)>)>() private var messageHistoryViews = Bag<(MutableMessageHistoryView, ValuePipe<(MessageHistoryView, ViewUpdateType)>)>() private var contactPeerIdsViews = Bag<(MutableContactPeerIdsView, ValuePipe)>() - private var contactPeersViews = Bag<(MutableContactPeersView, ValuePipe)>() private let messageHistoryHolesView = MutableMessageHistoryHolesView() private let messageHistoryHolesViewSubscribers = Bag>() @@ -112,17 +111,6 @@ final class ViewTracker { self.contactPeerIdsViews.remove(index) } - func addContactPeersView(_ view: MutableContactPeersView) -> (Bag<(MutableContactPeersView, ValuePipe)>.Index, Signal) { - let record = (view, ValuePipe()) - let index = self.contactPeersViews.add(record) - - return (index, record.1.signal()) - } - - func removeContactPeersView(_ index: Bag<(MutableContactPeersView, ValuePipe)>.Index) { - self.contactPeersViews.remove(index) - } - func addPeerView(_ view: MutablePeerView) -> (Bag<(MutablePeerView, ValuePipe)>.Index, Signal) { let record = (view, ValuePipe()) let index = self.peerViews.add(record) @@ -372,12 +360,6 @@ final class ViewTracker { } } - for (mutableView, pipe) in self.contactPeersViews.copyItems() { - if mutableView.replay(postbox: postbox, replacePeerIds: transaction.replaceContactPeerIds, updatedPeerPresences: transaction.currentUpdatedPeerPresences) { - pipe.putNext(ContactPeersView(mutableView)) - } - } - for (mutableView, pipe) in self.peerViews.copyItems() { if mutableView.replay(postbox: postbox, transaction: transaction) { pipe.putNext(PeerView(mutableView)) diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index 375b2519fa..3f47094ab6 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -31,6 +31,7 @@ public enum PostboxViewKey: Hashable { case allChatListHoles(PeerGroupId) case historyTagInfo(peerId: PeerId, tag: MessageTags) case topChatMessage(peerIds: [PeerId]) + case contacts(accountPeerId: PeerId?, includePresences: Bool) public func hash(into hasher: inout Hasher) { switch self { @@ -103,6 +104,8 @@ public enum PostboxViewKey: Hashable { hasher.combine(tag) case let .topChatMessage(peerIds): hasher.combine(peerIds) + case .contacts: + hasher.combine(16) } } @@ -288,6 +291,12 @@ public enum PostboxViewKey: Hashable { } else { return false } + case let .contacts(accountPeerId, includePresences): + if case .contacts(accountPeerId, includePresences) = rhs { + return true + } else { + return false + } } } } @@ -354,5 +363,7 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutableHistoryTagInfoView(postbox: postbox, peerId: peerId, tag: tag) case let .topChatMessage(peerIds): return MutableTopChatMessageView(postbox: postbox, peerIds: Set(peerIds)) + case let .contacts(accountPeerId, includePresences): + return MutableContactPeersView(postbox: postbox, accountPeerId: accountPeerId, includePresences: includePresences) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift index b69ef88237..7f97c75bd8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift @@ -51,5 +51,12 @@ public extension TelegramEngine { return peers.map(EngineRenderedPeer.init) } } + + public func searchContacts(query: String) -> Signal<([EnginePeer], [EnginePeer.Id: EnginePeer.Presence]), NoError> { + return self.account.postbox.searchContacts(query: query) + |> map { peers, presences in + return (peers.map(EnginePeer.init), presences.mapValues(EnginePeer.Presence.init)) + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/Contacts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/Contacts.swift new file mode 100644 index 0000000000..3b786bff35 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/Contacts.swift @@ -0,0 +1,37 @@ +import SwiftSignalKit +import Postbox + +public final class EngineContactList { + public let peers: [EnginePeer] + public let presences: [EnginePeer.Id: EnginePeer.Presence] + + public init(peers: [EnginePeer], presences: [EnginePeer.Id: EnginePeer.Presence]) { + self.peers = peers + self.presences = presences + } +} + +public extension TelegramEngine.EngineData.Item { + enum Contacts { + public struct List: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = EngineContactList + + private let includePresences: Bool + + public init(includePresences: Bool) { + self.includePresences = includePresences + } + + var key: PostboxViewKey { + return .contacts(accountPeerId: nil, includePresences: self.includePresences) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? ContactPeersView else { + preconditionFailure() + } + return EngineContactList(peers: view.peers.map(EnginePeer.init), presences: view.peerPresences.mapValues(EnginePeer.Presence.init)) + } + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift index f96d3d290c..15da45cd4b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift @@ -4,12 +4,54 @@ public enum EnginePeer: Equatable { public typealias Id = PeerId public struct Presence: Equatable { - public enum Status: Equatable { + public enum Status: Comparable { case present(until: Int32) case recently case lastWeek case lastMonth case longTimeAgo + + public static func <(lhs: Status, rhs: Status) -> Bool { + switch lhs { + case .longTimeAgo: + switch rhs { + case .longTimeAgo: + return false + case .lastMonth, .lastWeek, .recently, .present: + return true + } + case let .present(until): + switch rhs { + case .longTimeAgo: + return false + case let .present(rhsUntil): + return until < rhsUntil + case .lastWeek, .lastMonth, .recently: + return false + } + case .recently: + switch rhs { + case .longTimeAgo, .lastWeek, .lastMonth, .recently: + return false + case .present: + return true + } + case .lastWeek: + switch rhs { + case .longTimeAgo, .lastMonth, .lastWeek: + return false + case .present, .recently: + return true + } + case .lastMonth: + switch rhs { + case .longTimeAgo, .lastMonth: + return false + case .present, .recently, lastWeek: + return true + } + } + } } public var status: Status @@ -56,6 +98,38 @@ public enum EnginePeer: Equatable { } } + public enum IndexName: Equatable { + case title(title: String, addressName: String?) + case personName(first: String, last: String, addressName: String?, phoneNumber: String?) + + public var isEmpty: Bool { + switch self { + case let .title(title, addressName): + if !title.isEmpty { + return false + } + if let addressName = addressName, !addressName.isEmpty { + return false + } + return true + case let .personName(first, last, addressName, phoneNumber): + if !first.isEmpty { + return false + } + if !last.isEmpty { + return false + } + if let addressName = addressName, !addressName.isEmpty { + return false + } + if let phoneNumber = phoneNumber, !phoneNumber.isEmpty { + return false + } + return true + } + } + } + case user(TelegramUser) case legacyGroup(TelegramGroup) case channel(TelegramChannel) @@ -207,6 +281,60 @@ public extension EnginePeer.Presence { preconditionFailure() } } + + func _asPresence() -> TelegramUserPresence { + let mappedStatus: UserPresenceStatus + switch self.status { + case .longTimeAgo: + mappedStatus = .none + case let .present(until): + mappedStatus = .present(until: until) + case .recently: + mappedStatus = .recently + case .lastWeek: + mappedStatus = .lastWeek + case .lastMonth: + mappedStatus = .lastMonth + } + return TelegramUserPresence(status: mappedStatus, lastActivity: self.lastActivity) + } +} + +public extension EnginePeer.IndexName { + init(_ indexName: PeerIndexNameRepresentation) { + switch indexName { + case let .title(title, addressName): + self = .title(title: title, addressName: addressName) + case let .personName(first, last, addressName, phoneNumber): + self = .personName(first: first, last: last, addressName: addressName, phoneNumber: phoneNumber) + } + } + + func _asIndexName() -> PeerIndexNameRepresentation { + switch self { + case let .title(title, addressName): + return .title(title: title, addressName: addressName) + case let .personName(first, last, addressName, phoneNumber): + return .personName(first: first, last: last, addressName: addressName, phoneNumber: phoneNumber) + } + } + + func matchesByTokens(_ other: String) -> Bool { + return self._asIndexName().matchesByTokens(other) + } + + func stringRepresentation(lastNameFirst: Bool) -> String { + switch self { + case let .title(title, _): + return title + case let .personName(first, last, _, _): + if lastNameFirst { + return last + first + } else { + return first + last + } + } + } } public extension EnginePeer { @@ -218,8 +346,8 @@ public extension EnginePeer { return self._asPeer().addressName } - var indexName: PeerIndexNameRepresentation { - return self._asPeer().indexName + var indexName: EnginePeer.IndexName { + return EnginePeer.IndexName(self._asPeer().indexName) } var debugDisplayTitle: String { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index a57dba33ed..d1b2f3aad3 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -207,7 +207,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee } var sortedPeers = filteredInlineBots sortedPeers.append(contentsOf: filteredPeers.sorted(by: { lhs, rhs in - let result = lhs.indexName.indexName(.lastNameFirst).compare(rhs.indexName.indexName(.lastNameFirst)) + let result = lhs.indexName.stringRepresentation(lastNameFirst: true).compare(rhs.indexName.stringRepresentation(lastNameFirst: true)) return result == .orderedAscending })) sortedPeers = sortedPeers.filter { peer in @@ -391,7 +391,7 @@ func searchQuerySuggestionResultStateForChatInterfacePresentationState(_ chatPre let filteredPeers = peers var sortedPeers: [EnginePeer] = [] sortedPeers.append(contentsOf: filteredPeers.sorted(by: { lhs, rhs in - let result = lhs.indexName.indexName(.lastNameFirst).compare(rhs.indexName.indexName(.lastNameFirst)) + let result = lhs.indexName.stringRepresentation(lastNameFirst: true).compare(rhs.indexName.stringRepresentation(lastNameFirst: true)) return result == .orderedAscending })) return { _ in return .mentions(sortedPeers) } diff --git a/submodules/TelegramUI/Sources/SpotlightContacts.swift b/submodules/TelegramUI/Sources/SpotlightContacts.swift index 1ad6e86fbc..581686a4bb 100644 --- a/submodules/TelegramUI/Sources/SpotlightContacts.swift +++ b/submodules/TelegramUI/Sources/SpotlightContacts.swift @@ -187,11 +187,13 @@ private func manageableSpotlightContacts(appBasePath: String, accounts: Signal<[ return accounts |> mapToSignal { accounts -> Signal<[[PeerId: SpotlightIndexStorageItem]], NoError> in return combineLatest(queue: queue, accounts.map { account -> Signal<[PeerId: SpotlightIndexStorageItem], NoError> in - return account.postbox.contactPeersView(accountPeerId: account.peerId, includePresences: false) - |> map { view -> [PeerId: SpotlightIndexStorageItem] in - var result: [PeerId: SpotlightIndexStorageItem] = [:] + return TelegramEngine(account: account).data.subscribe( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: false) + ) + |> map { view -> [EnginePeer.Id: SpotlightIndexStorageItem] in + var result: [EnginePeer.Id: SpotlightIndexStorageItem] = [:] for peer in view.peers { - if let user = peer as? TelegramUser { + if case let .user(user) = peer { let avatarSourcePath = smallestImageRepresentation(user.photo).flatMap { representation -> String? in let resourcePath = account.postbox.mediaBox.resourcePath(representation.resource) if resourcePath.hasPrefix(appBasePath + "/") { @@ -208,8 +210,8 @@ private func manageableSpotlightContacts(appBasePath: String, accounts: Signal<[ |> distinctUntilChanged }) } - |> map { accountContacts -> [PeerId: SpotlightIndexStorageItem] in - var result: [PeerId: SpotlightIndexStorageItem] = [:] + |> map { accountContacts -> [EnginePeer.Id: SpotlightIndexStorageItem] in + var result: [EnginePeer.Id: SpotlightIndexStorageItem] = [:] for singleAccountContacts in accountContacts { for (peerId, contact) in singleAccountContacts { if result[peerId] == nil {