import HUD import UIKit import Shared import Models import Combine import Defaults import Integration import DependencyInjection enum SearchSection { case chats case connections } enum SearchItem: Equatable, Hashable { case chat(Chat) case connection(Contact) } typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, Contact> typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem> final class ChatListViewModel { @Dependency private var session: SessionType var isOnline: AnyPublisher<Bool, Never> { session.isOnline } var chatsPublisher: AnyPublisher<[Chat], Never> { chatsSubject.eraseToAnyPublisher() } var hudPublisher: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() } var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> { session.contacts(.isRecent).map { let section = SectionId() var snapshot = RecentsSnapshot() snapshot.appendSections([section]) snapshot.appendItems($0, toSection: section) return snapshot }.eraseToAnyPublisher() } var searchPublisher: AnyPublisher<SearchSnapshot, Never> { Publishers.CombineLatest3( session.contacts(.all), chatsPublisher, searchSubject .removeDuplicates() .debounce(for: .milliseconds(100), scheduler: DispatchQueue.main) .eraseToAnyPublisher() ) .map { (contacts, chats, query) in let connectionItems = contacts.filter { let username = $0.username.lowercased().contains(query.lowercased()) let nickname = $0.nickname?.lowercased().contains(query.lowercased()) ?? false return username || nickname }.map(SearchItem.connection) let chatItems = chats.filter { switch $0 { case .contact(let info): let username = info.contact.username.lowercased().contains(query.lowercased()) let nickname = info.contact.nickname?.lowercased().contains(query.lowercased()) ?? false let lastMessage = info.lastMessage?.payload.text.lowercased().contains(query.lowercased()) ?? false return username || nickname || lastMessage case .group(let info): let name = info.group.name.lowercased().contains(query.lowercased()) let last = info.lastMessage?.payload.text.lowercased().contains(query.lowercased()) ?? false return name || last } }.map(SearchItem.chat) var snapshot = SearchSnapshot() if connectionItems.count > 0 { snapshot.appendSections([.connections]) snapshot.appendItems(connectionItems, toSection: .connections) } if chatItems.count > 0 { snapshot.appendSections([.chats]) snapshot.appendItems(chatItems, toSection: .chats) } return snapshot }.eraseToAnyPublisher() } var badgeCountPublisher: AnyPublisher<Int, Never> { Publishers.CombineLatest( session.contacts(.received), session.groups(.pending) ) .map { $0.0.count + $0.1.count } .eraseToAnyPublisher() } private var cancellables = Set<AnyCancellable>() private let searchSubject = CurrentValueSubject<String, Never>("") private let chatsSubject = CurrentValueSubject<[Chat], Never>([]) private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none) init() { Publishers.CombineLatest( session.singleChats(.all), session.groupChats(.accepted) ).map { let groups = $0.1.map(Chat.group) let chats = $0.0.map(Chat.contact) return (chats + groups).sorted { $0.orderingDate > $1.orderingDate } } .sink { [unowned self] in chatsSubject.send($0) } .store(in: &cancellables) } func updateSearch(query: String) { searchSubject.send(query) } func leave(_ group: Group) { hudSubject.send(.on(nil)) do { try session.leave(group: group) session.deleteAll(from: group) hudSubject.send(.none) } catch { hudSubject.send(.error(.init(with: error))) } } func clear(_ contact: Contact) { session.deleteAll(from: contact) } }