import HUD import UIKit import Shared import Models import Combine import XXModels import Defaults import Integration import DependencyInjection enum SearchSection { case chats case connections } enum SearchItem: Equatable, Hashable { case chat(ChatInfo) 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<[ChatInfo], Never> { chatsSubject.eraseToAnyPublisher() } var hudPublisher: AnyPublisher<HUDStatus, Never> { hudSubject.eraseToAnyPublisher() } var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> { session.dbManager.fetchContactsPublisher(.init(isRecent: true)) .assertNoFailure() .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.dbManager.fetchContactsPublisher(.init()).assertNoFailure(), 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()) ?? false let nickname = $0.nickname?.lowercased().contains(query.lowercased()) ?? false return username || nickname }.map(SearchItem.connection) let chatItems = chats.filter { switch $0 { case .group(let group): return group.name.lowercased().contains(query.lowercased()) case .groupChat(let info): let name = info.group.name.lowercased().contains(query.lowercased()) let last = info.lastMessage.text.lowercased().contains(query.lowercased()) return name || last case .contactChat(let info): let username = info.contact.username?.lowercased().contains(query.lowercased()) ?? false let nickname = info.contact.nickname?.lowercased().contains(query.lowercased()) ?? false let lastMessage = info.lastMessage.text.lowercased().contains(query.lowercased()) return username || nickname || lastMessage } }.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> { let groupQuery = Group.Query(authStatus: [.pending]) let contactsQuery = Contact.Query(authStatus: [ .verified, .confirming, .confirmationFailed, .verificationFailed, .verificationInProgress ]) return Publishers.CombineLatest( session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(), session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure() ) .map { $0.0.count + $0.1.count } .eraseToAnyPublisher() } private var cancellables = Set<AnyCancellable>() private let searchSubject = CurrentValueSubject<String, Never>("") private let chatsSubject = CurrentValueSubject<[ChatInfo], Never>([]) private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none) init() { session.dbManager.fetchChatInfosPublisher( ChatInfo.Query( contactChatInfoQuery: .init( userId: session.myId, authStatus: [.friend] ), groupChatInfoQuery: GroupChatInfo.Query(), groupQuery: Group.Query(withMessages: false) )) .assertNoFailure() .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) try session.dbManager.deleteMessages(.init(chat: .group(group.id))) hudSubject.send(.none) } catch { hudSubject.send(.error(.init(with: error))) } } func clear(_ contact: Contact) { _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id))) } func groupInfo(from group: Group) -> GroupInfo? { let query = GroupInfo.Query(groupId: group.id) guard let info = try? session.dbManager.fetchGroupInfos(query).first else { return nil } return info } }