diff --git a/ConversationsClassic/AppData/Client/Client+MartinChats.swift b/ConversationsClassic/AppData/Client/Client+MartinChats.swift new file mode 100644 index 0000000..20befc7 --- /dev/null +++ b/ConversationsClassic/AppData/Client/Client+MartinChats.swift @@ -0,0 +1,75 @@ +import Foundation +import GRDB +import Martin + +final class ClientMartinChatsManager: Martin.ChatManager { + func chats(for context: Martin.Context) -> [any Martin.ChatProtocol] { + do { + let chats: [Chat] = try Database.shared.dbQueue.read { db in + try Chat.filter(Column("account") == context.userBareJid.stringValue).fetchAll(db) + } + return chats.map { chat in + Martin.ChatBase(context: context, jid: BareJID(chat.participant)) + } + } catch { + logIt(.error, "Error fetching chats: \(error.localizedDescription)") + return [] + } + } + + func chat(for context: Martin.Context, with: Martin.BareJID) -> (any Martin.ChatProtocol)? { + do { + let chat: Chat? = try Database.shared.dbQueue.read { db in + try Chat + .filter(Column("account") == context.userBareJid.stringValue) + .filter(Column("participant") == with.stringValue) + .fetchOne(db) + } + if chat != nil { + return Martin.ChatBase(context: context, jid: with) + } else { + return nil + } + } catch { + logIt(.error, "Error fetching chat: \(error.localizedDescription)") + return nil + } + } + + func createChat(for context: Martin.Context, with: Martin.BareJID) -> (any Martin.ChatProtocol)? { + do { + let chat: Chat? = try Database.shared.dbQueue.read { db in + try Chat + .filter(Column("account") == context.userBareJid.stringValue) + .filter(Column("participant") == with.stringValue) + .fetchOne(db) + } + if chat != nil { + return Martin.ChatBase(context: context, jid: with) + } else { + let chat = Chat( + id: UUID().uuidString, + account: context.userBareJid.stringValue, + participant: with.stringValue, + type: .chat + ) + try Database.shared.dbQueue.write { db in + try chat.save(db) + } + return Martin.ChatBase(context: context, jid: with) + } + } catch { + logIt(.error, "Error fetching chat: \(error.localizedDescription)") + return nil + } + } + + func close(chat: any Martin.ChatProtocol) -> Bool { + // not used in Martin library for now + print("Closing chat: \(chat)") + return false + } + + func initialize(context _: Martin.Context) {} + func deinitialize(context _: Martin.Context) {} +} diff --git a/ConversationsClassic/AppData/Client/Client.swift b/ConversationsClassic/AppData/Client/Client.swift index 509fdad..d57eae5 100644 --- a/ConversationsClassic/AppData/Client/Client.swift +++ b/ConversationsClassic/AppData/Client/Client.swift @@ -23,11 +23,12 @@ final class Client: ObservableObject { private var rostersCancellable: AnyCancellable? private var rosterManager = ClientMartinRosterManager() + private var chatsManager = ClientMartinChatsManager() init(credentials: Credentials) { self.credentials = credentials state = credentials.isActive ? .enabled(.disconnected) : .disabled - connection = Self.prepareConnection(credentials, rosterManager) + connection = Self.prepareConnection(credentials, rosterManager, chatsManager) connectionCancellable = connection.$state .sink { [weak self] state in guard let self = self else { return } @@ -92,7 +93,7 @@ extension Client { } private extension Client { - static func prepareConnection(_ credentials: Credentials, _ roster: RosterManager) -> XMPPClient { + static func prepareConnection(_ credentials: Credentials, _ roster: RosterManager, _ chat: ChatManager) -> XMPPClient { let client = XMPPClient() // register modules @@ -109,7 +110,7 @@ private extension Client { client.modulesManager.register(PresenceModule()) // client.modulesManager.register(PubSubModule()) - // client.modulesManager.register(MessageModule(chatManager: manager)) + client.modulesManager.register(MessageModule(chatManager: chat)) // client.modulesManager.register(MessageArchiveManagementModule()) // client.modulesManager.register(MessageCarbonsModule()) diff --git a/ConversationsClassic/AppData/Model/Chat.swift b/ConversationsClassic/AppData/Model/Chat.swift new file mode 100644 index 0000000..58fc273 --- /dev/null +++ b/ConversationsClassic/AppData/Model/Chat.swift @@ -0,0 +1,19 @@ +import Foundation +import GRDB + +enum ConversationType: Int, Codable, DatabaseValueConvertible { + case chat = 0 + case room = 1 + case channel = 2 +} + +struct Chat: DBStorable { + static let databaseTableName = "chats" + + var id: String + var account: String + var participant: String + var type: ConversationType +} + +extension Chat: Equatable {} diff --git a/ConversationsClassic/AppData/Services/Database+Migrations.swift b/ConversationsClassic/AppData/Services/Database+Migrations.swift index caf742d..3f59936 100644 --- a/ConversationsClassic/AppData/Services/Database+Migrations.swift +++ b/ConversationsClassic/AppData/Services/Database+Migrations.swift @@ -34,6 +34,14 @@ extension Database { table.primaryKey(["bareJid", "contactBareJid"], onConflict: .replace) table.column("locallyDeleted", .boolean).notNull().defaults(to: false) } + + // chats + try db.create(table: "chats", options: [.ifNotExists]) { table in + table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace) + table.column("account", .text).notNull() + table.column("participant", .text).notNull() + table.column("type", .integer).notNull() + } } // return migrator