Swiftgram/TelegramUI/ContactsController.swift
2016-08-23 16:21:34 +03:00

256 lines
9.0 KiB
Swift

import Foundation
import Display
import AsyncDisplayKit
import Postbox
import SwiftSignalKit
import TelegramCore
private enum ContactsControllerEntryId: Hashable {
case search
case vcard
case peerId(Int64)
var hashValue: Int {
switch self {
case .search:
return 0
case .vcard:
return 1
case let .peerId(peerId):
return peerId.hashValue
}
}
}
private func <(lhs: ContactsControllerEntryId, rhs: ContactsControllerEntryId) -> Bool {
return lhs.hashValue < rhs.hashValue
}
private func ==(lhs: ContactsControllerEntryId, rhs: ContactsControllerEntryId) -> Bool {
switch lhs {
case .search:
switch rhs {
case .search:
return true
default:
return false
}
case .vcard:
switch rhs {
case .vcard:
return true
default:
return false
}
case let .peerId(lhsId):
switch rhs {
case let .peerId(rhsId):
return lhsId == rhsId
default:
return false
}
}
}
private enum ContactsEntry: Comparable, Identifiable {
case search
case vcard(Peer)
case peer(Peer)
var stableId: ContactsControllerEntryId {
switch self {
case .search:
return .search
case .vcard:
return .vcard
case let .peer(peer):
return .peerId(peer.id.toInt64())
}
}
}
private func ==(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool {
switch lhs {
case .search:
switch rhs {
case .search:
return true
default:
return false
}
case let .vcard(lhsPeer):
switch rhs {
case let .vcard(rhsPeer):
return lhsPeer.id == rhsPeer.id
default:
return false
}
case let .peer(lhsPeer):
switch rhs {
case let .peer(rhsPeer):
return lhsPeer.id == rhsPeer.id
default:
return false
}
}
}
private func <(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
private func entriesForView(_ view: ContactPeersView) -> [ContactsEntry] {
var entries: [ContactsEntry] = []
entries.append(.search)
if let peer = view.accountPeer {
entries.append(.vcard(peer))
}
for peer in view.peers {
entries.append(.peer(peer))
}
return entries
}
public class ContactsController: ViewController {
private let queue = Queue()
private let account: Account
private let disposable = MetaDisposable()
private var entries: [ContactsEntry] = []
private var contactsNode: ContactsControllerNode {
return self.displayNode as! ContactsControllerNode
}
private let index: PeerNameIndex = .lastNameFirst
public init(account: Account) {
self.account = account
super.init()
self.title = "Contacts"
self.tabBarItem.title = "Contacts"
self.tabBarItem.image = UIImage(bundleImageName: "Chat List/Tabs/IconContacts")
self.tabBarItem.selectedImage = UIImage(bundleImageName: "Chat List/Tabs/IconContactsSelected")
self.disposable.set((account.postbox.contactPeersView(index: self.index, accountPeerId: account.peerId) |> deliverOn(self.queue)).start(next: { [weak self] view in
self?.updateView(view)
}))
self.scrollToTop = { [weak self] in
if let strongSelf = self, !strongSelf.entries.isEmpty {
strongSelf.contactsNode.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in })
}
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.disposable.dispose()
}
override public func loadDisplayNode() {
self.displayNode = ContactsControllerNode(account: self.account)
self.contactsNode.navigationBar = self.navigationBar
self.contactsNode.requestDeactivateSearch = { [weak self] in
self?.deactivateSearch()
}
self.contactsNode.requestOpenPeerFromSearch = { [weak self] peerId in
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: peerId))
}
}
self.displayNodeDidLoad()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationBar.frame.maxY, transition: transition)
}
private func updateView(_ view: ContactPeersView) {
assert(self.queue.isCurrent())
let previousEntries = self.entries
let updatedEntries = entriesForView(view)
let (deleteIndices, indicesAndItems) = mergeListsStable(leftList: previousEntries, rightList: updatedEntries)
self.entries = updatedEntries
var adjustedDeleteIndices: [ListViewDeleteItem] = []
if deleteIndices.count != 0 {
for index in deleteIndices {
adjustedDeleteIndices.append(ListViewDeleteItem(index: index, directionHint: nil))
}
}
var adjustedIndicesAndItems: [ListViewInsertItem] = []
for (index, entry, previousIndex) in indicesAndItems {
switch entry {
case .search:
adjustedIndicesAndItems.append(ListViewInsertItem(index: index, previousIndex: previousIndex, item: ChatListSearchItem(placeholder: "Search contacts", activate: { [weak self] in
self?.activateSearch()
}), directionHint: nil))
case let .vcard(peer):
adjustedIndicesAndItems.append(ListViewInsertItem(index: index, previousIndex: previousIndex, item: ContactsVCardItem(account: self.account, peer: peer, action: { [weak self] _ in
if let strongSelf = self {
strongSelf.entrySelected(entry)
strongSelf.contactsNode.listView.clearHighlightAnimated(true)
}
}), directionHint: nil))
case let .peer(peer):
adjustedIndicesAndItems.append(ListViewInsertItem(index: index, previousIndex: previousIndex, item: ContactsPeerItem(account: self.account, peer: peer, index: self.index, action: { [weak self] _ in
if let strongSelf = self {
strongSelf.entrySelected(entry)
strongSelf.contactsNode.listView.clearHighlightAnimated(true)
}
}), directionHint: nil))
}
}
DispatchQueue.main.async {
let options: ListViewDeleteAndInsertOptions = []
self.contactsNode.listView.deleteAndInsertItems(deleteIndices: adjustedDeleteIndices, insertIndicesAndItems: adjustedIndicesAndItems, updateIndicesAndItems: [], options: options, scrollToItem: nil, completion: { _ in
})
}
}
private func entrySelected(_ entry: ContactsEntry) {
if case let .peer(peer) = entry {
(self.navigationController as? NavigationController)?.pushViewController(ChatController(account: self.account, peerId: peer.id))
}
if case let .vcard(peer) = entry {
(self.navigationController as? NavigationController)?.pushViewController(ChatController(account: self.account, peerId: peer.id))
}
}
private func activateSearch() {
if self.displayNavigationBar {
if let scrollToTop = self.scrollToTop {
scrollToTop()
}
self.contactsNode.activateSearch()
self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
}
}
private func deactivateSearch() {
if !self.displayNavigationBar {
self.contactsNode.deactivateSearch()
self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring))
}
}
}