This commit is contained in:
fmodf 2024-06-26 12:26:04 +02:00
parent 995d627fde
commit 1e27b8643c
7 changed files with 149 additions and 47 deletions

View file

@ -1,4 +1,8 @@
enum XMPPAction: Codable { enum XMPPAction: Codable {
case clientConnectionChanged(jid: String, state: ConnectionStatus) case clientConnectionChanged(jid: String, state: ConnectionStatus)
case xmppMessageReceived(Message) case xmppMessageReceived(Message)
case xmppMessageSent(Message)
case xmppMessageSendFailed(msgId: String)
case xmppMessageSendSuccess(msgId: String)
} }

View file

@ -57,6 +57,7 @@ extension Database {
table.column("oobUrl", .text) table.column("oobUrl", .text)
table.column("date", .datetime).notNull() table.column("date", .datetime).notNull()
table.column("pending", .boolean).notNull() table.column("pending", .boolean).notNull()
table.column("sentError", .boolean).notNull()
} }
} }

View file

@ -184,7 +184,83 @@ final class DatabaseMiddleware {
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .conversationAction(.sendMessage(let from, let to, let body)): case .conversationAction(.sendMessage(let from, let to, let body)):
return Empty().eraseToAnyPublisher() return Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in
guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))))
return
}
do {
let message = Message(
id: UUID().uuidString,
type: .chat,
contentType: .text,
from: from,
to: to,
body: body,
subject: nil,
thread: nil,
oobUrl: nil,
date: Date(),
pending: true,
sentError: false
)
try database._db.write { db in
try message.insert(db)
}
promise(.success(.xmppAction(.xmppMessageSent(message))))
} catch {
promise(.success(.databaseAction(.storeMessageFailed(reason: error.localizedDescription))))
}
}
}
.eraseToAnyPublisher()
case .xmppAction(.xmppMessageSendSuccess(let msgId)):
// mark message as pending false and sentError false
return Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in
guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))))
return
}
do {
_ = try database._db.write { db in
try Message
.filter(Column("id") == msgId)
.updateAll(db, Column("pending").set(to: false), Column("sentError").set(to: false))
}
promise(.success(.empty))
} catch {
promise(.success(.databaseAction(.storeMessageFailed(reason: error.localizedDescription)))
)
}
}
}
.eraseToAnyPublisher()
case .xmppAction(.xmppMessageSendFailed(let msgId)):
// mark message as pending false and sentError true
return Future<AppAction, Never> { promise in
Task(priority: .background) { [weak self] in
guard let database = self?.database else {
promise(.success(.databaseAction(.storeMessageFailed(reason: L10n.Global.Error.genericDbError))))
return
}
do {
_ = try database._db.write { db in
try Message
.filter(Column("id") == msgId)
.updateAll(db, Column("pending").set(to: false), Column("sentError").set(to: true))
}
promise(.success(.empty))
} catch {
promise(.success(.databaseAction(.storeMessageFailed(reason: error.localizedDescription)))
)
}
}
}
.eraseToAnyPublisher()
default: default:
return Empty().eraseToAnyPublisher() return Empty().eraseToAnyPublisher()
@ -206,9 +282,7 @@ private extension DatabaseMiddleware {
.fetchAll .fetchAll
) )
.publisher(in: database._db, scheduling: .immediate) .publisher(in: database._db, scheduling: .immediate)
.sink { res in .sink { _ in
print("!!!---Messages received: \(res)")
// Handle completion
} receiveValue: { messages in } receiveValue: { messages in
DispatchQueue.main.async { DispatchQueue.main.async {
store.dispatch(.conversationAction(.messagesUpdated(messages: messages))) store.dispatch(.conversationAction(.messagesUpdated(messages: messages)))

View file

@ -86,13 +86,19 @@ final class XMPPMiddleware {
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
// case .conversationAction(.sendMessage(let from, let to, let body)): case .xmppAction(.xmppMessageSent(let message)):
// return Future<AppAction, Never> { [weak self] promise in return Future<AppAction, Never> { [weak self] promise in
// // TODO: handle errors DispatchQueue.global().async {
// self?.service.sendMessage(from: from, to: to, body: body) self?.service.sendMessage(message: message) { done in
// promise(.success(.empty)) if done {
// } promise(.success(.xmppAction(.xmppMessageSendSuccess(msgId: message.id))))
// .eraseToAnyPublisher() } else {
promise(.success(.xmppAction(.xmppMessageSendFailed(msgId: message.id))))
}
}
}
}
.eraseToAnyPublisher()
default: default:
return Empty().eraseToAnyPublisher() return Empty().eraseToAnyPublisher()

View file

@ -31,6 +31,7 @@ struct Message: DBStorable, Equatable {
let date: Date let date: Date
let pending: Bool let pending: Bool
let sentError: Bool
} }
extension Message { extension Message {
@ -76,7 +77,8 @@ extension Message {
thread: martinMessage.thread, thread: martinMessage.thread,
oobUrl: martinMessage.oob, oobUrl: martinMessage.oob,
date: Date(), date: Date(),
pending: false pending: false,
sentError: false
) )
return msg return msg
} }

View file

@ -104,16 +104,25 @@ final class XMPPService: ObservableObject {
clients.first { $0.connectionConfiguration.userJid.stringValue == jid } clients.first { $0.connectionConfiguration.userJid.stringValue == jid }
} }
// TODO: add handler func sendMessage(message: Message, completion: @escaping (Bool) -> Void) {
func sendMessage(from: String, to: String, body: String) { guard let client = getClient(for: message.from), let to = message.to else {
guard let client = getClient(for: from) else { return } completion(false)
return
}
let message = Martin.Message() let message = Martin.Message()
// message.from = client.connectionConfiguration.userJid
message.to = JID(to) message.to = JID(to)
message.body = body message.body = message.body
client.writer.write(message) { res in client.module(MessageModule.self)
print(res) .chatManager
.chat(for: client.context, with: JID(to).bareJid)?
.send(message: message) { res in
switch res {
case .success:
completion(true)
case .failure:
completion(false)
}
} }
} }
} }

View file

@ -129,20 +129,14 @@ private struct ConversationMessageRow: View {
HStack(spacing: 0) { HStack(spacing: 0) {
if isOutgoing() { if isOutgoing() {
Spacer() Spacer()
VStack(spacing: 0) { MessageAttr(message: message)
MessageTime(message: message)
Spacer()
}
.padding(.trailing, 4) .padding(.trailing, 4)
} }
MessageContainer(message: message, isOutgoing: isOutgoing()) MessageContainer(message: message, isOutgoing: isOutgoing())
.background(isOutgoing() ? Color.Material.greenDark100 : Color.Main.white) .background(isOutgoing() ? Color.Material.greenDark100 : Color.Main.white)
.clipShape(MessageBubble(isOutgoing: isOutgoing())) .clipShape(MessageBubble(isOutgoing: isOutgoing()))
if !isOutgoing() { if !isOutgoing() {
VStack(spacing: 0) { MessageAttr(message: message)
MessageTime(message: message)
Spacer()
}
.padding(.leading, 4) .padding(.leading, 4)
Spacer() Spacer()
} }
@ -184,13 +178,25 @@ struct MessageBubble: Shape {
} }
} }
struct MessageTime: View { struct MessageAttr: View {
let message: Message let message: Message
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text(message.date, style: .time) Text(message.date, style: .time)
.font(.sub2) .font(.sub2)
.foregroundColor(Color.Main.gray) .foregroundColor(Color.Main.gray)
Spacer()
if message.sentError {
Image(systemName: "exclamationmark.circle")
.font(.body3)
.foregroundColor(Color.Tango.redLight)
} else if message.pending {
Image(systemName: "clock")
.font(.body3)
.foregroundColor(Color.Main.gray)
}
}
} }
} }
@ -226,13 +232,13 @@ struct MessageTime: View {
thread: nil, thread: nil,
oobUrl: nil, oobUrl: nil,
date: Date(), date: Date(),
pending: false pending: true, sentError: false
), ),
Message(id: "2", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for testsdfsdf sdfsdf sdfs sdf sdffsdf sdf sdf sdf sdf sdf sdff sdfffwwe ", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false), Message(id: "2", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for testsdfsdf sdfsdf sdfs sdf sdffsdf sdf sdf sdf sdf sdf sdff sdfffwwe ", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false),
Message(id: "3", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false), Message(id: "3", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: true),
Message(id: "4", type: .chat, contentType: .text, from: acc, to: contact, body: "this is for test sdfkjwek jwkjfh jwerf jdfhskjdhf jsdhfjhwefh sjdhfh fsdjhfh sd ", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false), Message(id: "4", type: .chat, contentType: .text, from: acc, to: contact, body: "this is for test sdfkjwek jwkjfh jwerf jdfhskjdhf jsdhfjhwefh sjdhfh fsdjhfh sd ", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false),
Message(id: "5", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for test sdfjkkeke kekkddjw;; w;edkdjfj l kjwekrjfk wef", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false), Message(id: "5", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for test sdfjkkeke kekkddjw;; w;edkdjfj l kjwekrjfk wef", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false),
Message(id: "6", type: .chat, contentType: .text, from: acc, to: contact, body: "this is for testsdf dsdkkekkddn wejkjfj ", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false), Message(id: "6", type: .chat, contentType: .text, from: acc, to: contact, body: "this is for testsdf dsdkkekkddn wejkjfj ", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false),
Message( Message(
id: "7", id: "7",
type: .chat, type: .chat,
@ -244,12 +250,12 @@ struct MessageTime: View {
subject: nil, subject: nil,
thread: nil, thread: nil,
oobUrl: nil, oobUrl: nil,
date: Date(), pending: false date: Date(), pending: false, sentError: false
), ),
Message(id: "8", type: .chat, contentType: .text, from: acc, to: contact, body: "so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false), Message(id: "8", type: .chat, contentType: .text, from: acc, to: contact, body: "so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false),
Message(id: "9", type: .chat, contentType: .text, from: contact, to: acc, body: "so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false), Message(id: "9", type: .chat, contentType: .text, from: contact, to: acc, body: "so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false),
Message(id: "10", type: .chat, contentType: .text, from: acc, to: contact, body: "so test so test so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false), Message(id: "10", type: .chat, contentType: .text, from: acc, to: contact, body: "so test so test so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false),
Message(id: "11", type: .chat, contentType: .text, from: contact, to: acc, body: "xD", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false) Message(id: "11", type: .chat, contentType: .text, from: contact, to: acc, body: "xD", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false)
] ]
return state return state
} }