import Foundation import monalxmpp enum AccountsAvailability { case noAccounts case allDisabled case someEnabled } final class WrapperXMPP: ObservableObject { @Published private(set) var accountsAvailability: AccountsAvailability = .noAccounts @Published private(set) var accounts: [Account] = [] @Published private(set) var contacts: [Contact] = [] @Published private(set) var activeChats: [Chat] = [] private let xmpp: MLXMPPManager private let db: DataLayer private var notificationObservers: [AnyObject] = [] init() { // init monalxmpp components xmpp = MLXMPPManager.sharedInstance() db = DataLayer.sharedInstance() // subscribe to monalxmpp notifications and fire notification for update subscribeToUpdates() xmpp.reconnectAll() NotificationCenter.default.post(name: Notification.Name(kMonalRefresh), object: nil) } deinit { notificationObservers.forEach { NotificationCenter.default.removeObserver($0) } } } // MARK: - Public extension WrapperXMPP { func tryLogin(_ login: String, _ password: String) async throws { let scenario = ScenarioLogIn() let result = await scenario.tryLogin(login, password) if !result { throw AimErrors.loginError } else { NotificationCenter.default.post(name: Notification.Name(kMonalRefresh), object: nil) } } func addContact(contactJid: String, forAccountID: Int) async { let contact = MLContact.createContact(fromJid: contactJid, andAccountID: NSNumber(value: forAccountID)) xmpp.add(contact) } func deleteContact(_ contact: Contact) async throws { if let mlContact = db.contactList().first(where: { $0.contactJid == contact.contactJid }) { xmpp.remove(mlContact) } else { throw AimErrors.contactRemoveError } } func chat(with: Contact) -> WrapperChat { // swiftlint:disable:next force_unwrapping let account = accounts.first { $0.id == with.ownerId }! let chatModel = WrapperChat(account: account, contact: with, db: db, xmpp: xmpp) return chatModel } func chat(with: Chat) -> WrapperChat? { guard let account = accounts.first(where: { $0.id == with.accountId }) else { return nil } var contact = contacts.first(where: { $0.ownerId == with.accountId && $0.contactJid == with.participantJid }) if contact == nil { let semaphore = DispatchSemaphore(value: 0) Task { await addContact(contactJid: with.participantJid, forAccountID: with.accountId) semaphore.signal() } semaphore.wait() refreshChats() refreshContacts() contact = contacts.first(where: { $0.ownerId == with.accountId && $0.contactJid == with.participantJid }) } guard let contact else { return nil } let chatModel = WrapperChat(account: account, contact: contact, db: db, xmpp: xmpp) return chatModel } } // MARK: - Handle notifications private extension WrapperXMPP { func subscribeToUpdates() { // General let generalRefresh = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalRefresh), object: nil, queue: .main) { [weak self] _ in self?.refreshAccounts() self?.refreshContacts() self?.refreshChats() } notificationObservers.append(generalRefresh) // For contacts let contactRefresh = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalContactRefresh), object: nil, queue: .main) { [weak self] _ in self?.refreshContacts() self?.refreshChats() } let contactRemove = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalContactRemoved), object: nil, queue: .main) { [weak self] _ in self?.refreshContacts() self?.refreshChats() } notificationObservers.append(contentsOf: [contactRefresh, contactRemove]) } func refreshAccounts() { let accounts = db.accountList() .compactMap { dict -> Account? in guard let dict = dict as? NSDictionary else { return nil } return Account(dict) } self.accounts = accounts xmpp.connectIfNecessary() // mark accounts availability if accounts.isEmpty { accountsAvailability = .noAccounts } else if accounts.filter({ $0.isEnabled }).isEmpty { accountsAvailability = .allDisabled } else { accountsAvailability = .someEnabled } } func refreshContacts() { contacts = db.contactList() .filter { $0.isSubscribedTo || $0.hasOutgoingContactRequest || $0.isSubscribedFrom } .filter { !$0.isSelfChat } // removed for now .compactMap { Contact($0) } } func refreshChats() { activeChats = db.activeContacts(withPinned: false) .compactMap { guard let contact = $0 as? MLContact else { return nil } return Chat(contact) } } }