This commit is contained in:
fmodf 2024-06-24 12:44:55 +02:00
parent 9f91741354
commit 8ce21712b7
10 changed files with 148 additions and 22 deletions

View file

@ -10,4 +10,5 @@ enum AppAction: Codable {
case rostersAction(_ action: RostersAction) case rostersAction(_ action: RostersAction)
case chatsAction(_ action: ChatsAction) case chatsAction(_ action: ChatsAction)
case conversationAction(_ action: ConversationAction) case conversationAction(_ action: ConversationAction)
case messagesAction(_ action: MessagesAction)
} }

View file

@ -0,0 +1,4 @@
enum MessagesAction: Codable {
case newMessageReceived(Message)
case messageDraftUpdate(Message)
}

View file

@ -52,6 +52,7 @@ extension Database {
table.column("toJid", .text).notNull() table.column("toJid", .text).notNull()
table.column("timestamp", .datetime).notNull() table.column("timestamp", .datetime).notNull()
table.column("body", .text) table.column("body", .text)
table.column("type", .text).notNull()
// table.column("isReaded", .boolean).notNull().defaults(to: false) // table.column("isReaded", .boolean).notNull().defaults(to: false)
// table.column("subject", .text) // table.column("subject", .text)
// table.column("threadId", .text) // table.column("threadId", .text)

View file

@ -0,0 +1,12 @@
import Combine
final class MessagesMiddleware {
static let shared = MessagesMiddleware()
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action {
default:
return Empty().eraseToAnyPublisher()
}
}
}

View file

@ -17,6 +17,26 @@ final class XMPPMiddleware {
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
service.clientMessages.sink { client, martinMessage in
print("---")
print("Message received: \(martinMessage)")
print("In client: \(client)")
print("---")
// guard let message = Message.mapMartinMessage(martinMessage) else {
// return
// }
// if message.type == .writingProcessUpdate {
// DispatchQueue.main.async {
// store.dispatch(.messagesAction(.messageDraftUpdate(message)))
// }
// } else {
// DispatchQueue.main.async {
// store.dispatch(.messagesAction(.newMessageReceived(message)))
// }
// }
}
.store(in: &cancellables)
} }
func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> { func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {

View file

@ -1,30 +1,98 @@
import Foundation import Foundation
import GRDB import GRDB
import Martin
enum MessageType: String, Codable, DatabaseValueConvertible { enum MessageType: String, Codable, DatabaseValueConvertible {
case chat
case channel
case groupchat
}
enum MessageContentType: String, Codable, DatabaseValueConvertible {
case text case text
case image case image
case video case video
case audio case audio
case file case file
case location case location
case typing
case invite
} }
struct Message: DBStorable, Equatable { struct MessageContainer: Stateable, DatabaseValueConvertible {
static let databaseTableName = "messages"
let id: String let id: String
let chatId: String let type: MessageType
let fromJid: String let content: any MessageContent
let toJid: String }
let timestamp: Date
let body: String?
// var isReaded: Bool
// let subject: String?
// let threadId: String?
// let errorType: String?
var type: MessageType { protocol MessageContent: Stateable, DatabaseValueConvertible {
.text var type: MessageContentType { get }
}
} }
//
// enum MessageType: String, Codable, DatabaseValueConvertible {
// case text
// case image
// case video
// case audio
// case file
// case location
// case writingProcessUpdate
// }
//
// struct Message: DBStorable, Equatable {
// static let databaseTableName = "messages"
//
// let id: String
// let chatId: String
// let fromJid: String
// let toJid: String
// let timestamp: Date
// let body: String?
// let type: MessageType
// }
//
// // Special extnesion for mapping Martin.Message to Message
// extension Message {
// static func mapMartinMessage(_ martinMessage: Martin.Message) -> Message? {
// // for draft messages
// if martinMessage.hints.contains(.noStore) {
// return Message(
// id: martinMessage.id ?? UUID().uuidString,
// chatId: "none", // chat id will be filled later
// fromJid: martinMessage.from?.bareJid.stringValue ?? "",
// toJid: martinMessage.to?.bareJid.stringValue ?? "",
// timestamp: Date(),
// body: nil,
// type: .writingProcessUpdate
// )
// }
//
// // if regular message contains no body - return nil
// guard let body = martinMessage.body else {
// return nil
// }
//
// print("Message received: \(martinMessage)")
// print("From: \(martinMessage.from)")
// print("To: \(martinMessage.to)")
// print("Body: \(martinMessage.body)")
// print("Type: \(martinMessage.type)")
// print("Id: \(martinMessage.id)")
// print("Subject: \(martinMessage.subject)")
// print("----")
// print("!!!!!-----Message body: \(body)")
//
// // parse regular message
// return nil
// // Message(
// // id: message.id,
// // chatId: message.chatId,
// // fromJid: message.from,
// // toJid: message.to,
// // timestamp: message.timestamp,
// // body: message.body,
// // type: MessageType(rawValue: message.type) ?? .text
// // )
// }
// }

View file

@ -27,6 +27,9 @@ extension AppState {
case .conversationAction(let action): case .conversationAction(let action):
ConversationState.reducer(state: &state.conversationsState, action: action) ConversationState.reducer(state: &state.conversationsState, action: action)
case .messagesAction:
break // messages actions are processed by MessagesMiddleware, and other components
} }
} }
} }

View file

@ -1,11 +1,11 @@
struct ConversationState: Stateable { struct ConversationState: Stateable {
var currentChat: Chat? var currentChat: Chat?
var dumb: Bool var currentMessages: [Message]
} }
// MARK: Init // MARK: Init
extension ConversationState { extension ConversationState {
init() { init() {
dumb = false currentMessages = []
} }
} }

View file

@ -8,13 +8,19 @@ protocol MartinsManager: Martin.RosterManager & Martin.ChatManager {}
final class XMPPService: ObservableObject { final class XMPPService: ObservableObject {
private let manager: MartinsManager private let manager: MartinsManager
private let clientStatePublisher = PassthroughSubject<(XMPPClient, XMPPClient.State), Never>() private let clientStatePublisher = PassthroughSubject<(XMPPClient, XMPPClient.State), Never>()
private var clientStateCancellables: [AnyCancellable] = [] private let clientMessagesPublisher = PassthroughSubject<(XMPPClient, Martin.Message), Never>()
private var clientStateCancellables: Set<AnyCancellable> = []
private var clientMessagesCancellables: Set<AnyCancellable> = []
@Published private(set) var clients: [XMPPClient] = [] @Published private(set) var clients: [XMPPClient] = []
var clientState: AnyPublisher<(XMPPClient, XMPPClient.State), Never> { var clientState: AnyPublisher<(XMPPClient, XMPPClient.State), Never> {
clientStatePublisher.eraseToAnyPublisher() clientStatePublisher.eraseToAnyPublisher()
} }
var clientMessages: AnyPublisher<(XMPPClient, Martin.Message), Never> {
clientMessagesPublisher.eraseToAnyPublisher()
}
init(manager: MartinsManager) { init(manager: MartinsManager) {
self.manager = manager self.manager = manager
} }
@ -29,14 +35,24 @@ final class XMPPService: ObservableObject {
// init and add clients // init and add clients
for account in forAdd { for account in forAdd {
// add client
let client = makeClient(for: account, with: manager) let client = makeClient(for: account, with: manager)
clients.append(client) clients.append(client)
let cancellable = client.$state
// subscribe to client state
client.$state
.sink { [weak self] state in .sink { [weak self] state in
self?.clientStatePublisher.send((client, state)) self?.clientStatePublisher.send((client, state))
} }
.store(in: &clientStateCancellables)
// subscribe to client messages
client.module(MessageModule.self).messagesPublisher
.sink { [weak self] message in
self?.clientMessagesPublisher.send((client, message.message))
}
.store(in: &clientMessagesCancellables)
clientStateCancellables.append(cancellable)
client.login() client.login()
} }

View file

@ -13,7 +13,8 @@ let store = AppStore(
XMPPMiddleware.shared.middleware, XMPPMiddleware.shared.middleware,
RostersMiddleware.shared.middleware, RostersMiddleware.shared.middleware,
ChatsMiddleware.shared.middleware, ChatsMiddleware.shared.middleware,
ConversationMiddleware.shared.middleware ConversationMiddleware.shared.middleware,
MessagesMiddleware.shared.middleware
] ]
) )