Skip to content
Snippets Groups Projects
ChatListViewModel.swift 5.68 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import HUD
Ahmed Shehata's avatar
Ahmed Shehata committed
import UIKit
Bruno Muniz's avatar
Bruno Muniz committed
import Shared
import Models
import Combine
import XXModels
Bruno Muniz's avatar
Bruno Muniz committed
import Defaults
import Integration
import DependencyInjection

Ahmed Shehata's avatar
Ahmed Shehata committed
enum SearchSection {
    case chats
    case connections
}
Bruno Muniz's avatar
Bruno Muniz committed

enum SearchItem: Equatable, Hashable {
    case chat(ChatInfo)
    case connection(Contact)
}
Bruno Muniz's avatar
Bruno Muniz committed

Ahmed Shehata's avatar
Ahmed Shehata committed
typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>
typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
Ahmed Shehata's avatar
Ahmed Shehata committed

final class ChatListViewModel {
Bruno Muniz's avatar
Bruno Muniz committed
    @Dependency private var session: SessionType

Ahmed Shehata's avatar
Ahmed Shehata committed
    var isOnline: AnyPublisher<Bool, Never> {
        session.isOnline
    }
Bruno Muniz's avatar
Bruno Muniz committed

    var chatsPublisher: AnyPublisher<[ChatInfo], Never> {
        chatsSubject.eraseToAnyPublisher()
    }
Bruno Muniz's avatar
Bruno Muniz committed

Ahmed Shehata's avatar
Ahmed Shehata committed
    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 {
Bruno Muniz's avatar
Bruno Muniz committed
                    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()
    }
Bruno Muniz's avatar
Bruno Muniz committed

Ahmed Shehata's avatar
Ahmed Shehata committed
    private var cancellables = Set<AnyCancellable>()
    private let searchSubject = CurrentValueSubject<String, Never>("")
    private let chatsSubject = CurrentValueSubject<[ChatInfo], Never>([])
Ahmed Shehata's avatar
Ahmed Shehata committed
    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
Bruno Muniz's avatar
Bruno Muniz committed

    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)
Ahmed Shehata's avatar
Ahmed Shehata committed
    func updateSearch(query: String) {
        searchSubject.send(query)
Ahmed Shehata's avatar
Ahmed Shehata committed
    func leave(_ group: Group) {
        hudSubject.send(.on(nil))
Bruno Muniz's avatar
Bruno Muniz committed

        do {
            try session.leave(group: group)
            try session.dbManager.deleteMessages(.init(chat: .group(group.id)))
Ahmed Shehata's avatar
Ahmed Shehata committed
            hudSubject.send(.none)
Bruno Muniz's avatar
Bruno Muniz committed
        } catch {
Ahmed Shehata's avatar
Ahmed Shehata committed
            hudSubject.send(.error(.init(with: error)))
Ahmed Shehata's avatar
Ahmed Shehata committed
    func clear(_ contact: Contact) {
        _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id)))
Bruno Muniz's avatar
Bruno Muniz committed
    }
Bruno Muniz's avatar
Bruno Muniz committed

    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
    }
Bruno Muniz's avatar
Bruno Muniz committed
}