wip
This commit is contained in:
parent
8ce21712b7
commit
9b4323ccd3
|
@ -1,3 +1,4 @@
|
|||
enum ConversationAction: Codable {
|
||||
case makeConversationActive(chat: Chat)
|
||||
case messageForCurrentConversationReceived(Message)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
enum MessagesAction: Codable {
|
||||
case newMessageReceived(Message)
|
||||
case messageDraftUpdate(Message)
|
||||
case dumb
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
enum XMPPAction: Codable {
|
||||
case clientConnectionChanged(jid: String, state: ConnectionStatus)
|
||||
case xmppMessageReceived(Message)
|
||||
}
|
||||
|
|
|
@ -47,16 +47,14 @@ extension Database {
|
|||
// messages
|
||||
try db.create(table: "messages", options: [.ifNotExists]) { table in
|
||||
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
|
||||
table.column("chatId", .text).notNull().references("chats", onDelete: .cascade)
|
||||
table.column("fromJid", .text).notNull()
|
||||
table.column("toJid", .text).notNull()
|
||||
table.column("timestamp", .datetime).notNull()
|
||||
table.column("body", .text)
|
||||
table.column("type", .text).notNull()
|
||||
// table.column("isReaded", .boolean).notNull().defaults(to: false)
|
||||
// table.column("subject", .text)
|
||||
// table.column("threadId", .text)
|
||||
// table.column("errorType", .text)
|
||||
table.column("contentType", .text).notNull()
|
||||
table.column("from", .text).notNull()
|
||||
table.column("to", .text)
|
||||
table.column("body", .text)
|
||||
table.column("subject", .text)
|
||||
table.column("thread", .text)
|
||||
table.column("oobUrl", .text)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import Combine
|
|||
final class ConversationMiddleware {
|
||||
static let shared = ConversationMiddleware()
|
||||
|
||||
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
|
||||
func middleware(state: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
|
||||
switch action {
|
||||
case .chatsAction(.chatStarted(let chat)):
|
||||
return Just(AppAction.conversationAction(.makeConversationActive(chat: chat))).eraseToAnyPublisher()
|
||||
|
@ -11,6 +11,17 @@ final class ConversationMiddleware {
|
|||
case .conversationAction(.makeConversationActive):
|
||||
return Just(AppAction.changeFlow(.conversation)).eraseToAnyPublisher()
|
||||
|
||||
case .xmppAction(.xmppMessageReceived(let message)):
|
||||
return Future<AppAction, Never> { promise in
|
||||
let currentChat = state.conversationsState.currentChat
|
||||
if message.from == currentChat?.participant, message.to == currentChat?.account {
|
||||
promise(.success(.conversationAction(.messageForCurrentConversationReceived(message))))
|
||||
} else {
|
||||
promise(.success(.empty))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
default:
|
||||
return Empty().eraseToAnyPublisher()
|
||||
}
|
||||
|
|
|
@ -150,6 +150,13 @@ final class DatabaseMiddleware {
|
|||
}
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
case .xmppAction(.xmppMessageReceived(let message)):
|
||||
if message.type != .chat {
|
||||
return Empty().eraseToAnyPublisher()
|
||||
}
|
||||
// TODO: Store msg here!
|
||||
return Empty().eraseToAnyPublisher()
|
||||
|
||||
default:
|
||||
return Empty().eraseToAnyPublisher()
|
||||
}
|
||||
|
|
|
@ -18,23 +18,11 @@ final class XMPPMiddleware {
|
|||
}
|
||||
.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)))
|
||||
// }
|
||||
// }
|
||||
service.clientMessages.sink { _, martinMessage in
|
||||
guard let message = Message.map(martinMessage) else { return }
|
||||
DispatchQueue.main.async {
|
||||
store.dispatch(.xmppAction(.xmppMessageReceived(message)))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
|
|
@ -4,95 +4,73 @@ import Martin
|
|||
|
||||
enum MessageType: String, Codable, DatabaseValueConvertible {
|
||||
case chat
|
||||
case channel
|
||||
case groupchat
|
||||
case error
|
||||
}
|
||||
|
||||
enum MessageContentType: String, Codable, DatabaseValueConvertible {
|
||||
case text
|
||||
case image
|
||||
case video
|
||||
case audio
|
||||
case file
|
||||
case location
|
||||
case typing
|
||||
case invite
|
||||
}
|
||||
|
||||
struct MessageContainer: Stateable, DatabaseValueConvertible {
|
||||
struct Message: Stateable, Identifiable, DatabaseValueConvertible {
|
||||
let id: String
|
||||
let type: MessageType
|
||||
let content: any MessageContent
|
||||
let contentType: MessageContentType
|
||||
|
||||
let from: String
|
||||
let to: String?
|
||||
|
||||
let body: String?
|
||||
let subject: String?
|
||||
let thread: String?
|
||||
let oobUrl: String?
|
||||
}
|
||||
|
||||
protocol MessageContent: Stateable, DatabaseValueConvertible {
|
||||
var type: MessageContentType { get }
|
||||
}
|
||||
extension Message {
|
||||
// Universal mapping from Martin's Message to App Message
|
||||
static func map(_ martinMessage: Martin.Message) -> Message? {
|
||||
#if DEBUG
|
||||
print("---")
|
||||
print("Message received: \(martinMessage)")
|
||||
print("---")
|
||||
#endif
|
||||
|
||||
//
|
||||
// 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
|
||||
// // )
|
||||
// }
|
||||
// }
|
||||
// Check that the message type is supported
|
||||
let chatTypes: [StanzaType] = [.chat, .groupchat]
|
||||
guard let mType = martinMessage.type, chatTypes.contains(mType) else {
|
||||
#if DEBUG
|
||||
print("Unsupported message type: \(martinMessage.type?.rawValue ?? "nil")")
|
||||
#endif
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type
|
||||
let type = MessageType(rawValue: martinMessage.type?.rawValue ?? "") ?? .chat
|
||||
|
||||
// Content type
|
||||
var contentType: MessageContentType = .text
|
||||
if martinMessage.hints.contains(.noStore) {
|
||||
contentType = .typing
|
||||
}
|
||||
|
||||
// From/To
|
||||
let from = martinMessage.from?.bareJid.stringValue ?? ""
|
||||
let to = martinMessage.to?.bareJid.stringValue
|
||||
|
||||
// Msg
|
||||
let msg = Message(
|
||||
id: martinMessage.id ?? UUID().uuidString,
|
||||
type: type,
|
||||
contentType: contentType,
|
||||
from: from,
|
||||
to: to,
|
||||
body: martinMessage.body,
|
||||
subject: martinMessage.subject,
|
||||
thread: martinMessage.thread,
|
||||
oobUrl: martinMessage.oob
|
||||
)
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ extension ConversationState {
|
|||
case .makeConversationActive(let chat):
|
||||
state.currentChat = chat
|
||||
|
||||
case .messageForCurrentConversationReceived(let message):
|
||||
state.currentMessages.append(message)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
22
ConversationsClassic/View/Components/MessageContainer.swift
Normal file
22
ConversationsClassic/View/Components/MessageContainer.swift
Normal file
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct MessageContainer: View {
|
||||
let message: Message
|
||||
let isOutgoing: Bool
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// bg
|
||||
Color.Main.backgroundDark
|
||||
.ignoresSafeArea()
|
||||
|
||||
// TODO: make custom body for different message types
|
||||
// body
|
||||
Text(message.body ?? "...")
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(Color.Main.black)
|
||||
.background(isOutgoing ? Color.Material.greenDark200 : Color.Main.white)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,21 +7,30 @@ struct ConversationScreen: View {
|
|||
@EnvironmentObject var store: AppStore
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// Header
|
||||
ConversationScreenHeader()
|
||||
ZStack {
|
||||
// Background color
|
||||
Color.Main.backgroundLight
|
||||
.ignoresSafeArea()
|
||||
|
||||
// Msg list
|
||||
// if !state.messages.isEmpty {
|
||||
// List {
|
||||
// ForEach(state.messages) { message in
|
||||
// ChatMessageView(message: message)
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// Text("No messages")
|
||||
// Spacer()
|
||||
// }
|
||||
// Content
|
||||
VStack(spacing: 0) {
|
||||
// Header
|
||||
ConversationScreenHeader()
|
||||
|
||||
// Msg list
|
||||
let messages = store.state.conversationsState.currentMessages
|
||||
if !messages.isEmpty {
|
||||
List {
|
||||
ForEach(messages) { message in
|
||||
ConversationMessageRow(message: message)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.background(Color.Main.backgroundLight)
|
||||
} else {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,50 +72,62 @@ private struct ConversationScreenHeader: View {
|
|||
}
|
||||
}
|
||||
|
||||
private struct ConversationMessageView: View {
|
||||
// @EnvironmentObject var state: AppState
|
||||
private struct ConversationMessageRow: View {
|
||||
@EnvironmentObject var store: AppStore
|
||||
|
||||
let message: Message
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text(message.body ?? "--NO BODY?--")
|
||||
// .padding(.all, 8)
|
||||
// .background(.black)
|
||||
// .clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.foregroundColor(Color.Main.black)
|
||||
Spacer()
|
||||
VStack {
|
||||
if isIncoming() {
|
||||
HStack {
|
||||
MessageContainer(message: message, isOutgoing: false)
|
||||
.padding(.all, 8)
|
||||
Spacer()
|
||||
.frame(minWidth: 48, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
} else {
|
||||
HStack {
|
||||
Spacer()
|
||||
.frame(minWidth: 48, maxWidth: .infinity, alignment: .leading)
|
||||
MessageContainer(message: message, isOutgoing: true)
|
||||
.padding(.all, 8)
|
||||
}
|
||||
}
|
||||
// HStack {
|
||||
// if isIncoming() {
|
||||
// Image(systemName: "person.fill")
|
||||
// .foregroundColor(Color.Main.black)
|
||||
// .frame(width: 32, height: 32)
|
||||
// .background(Color.Main.backgroundLight)
|
||||
// .clipShape(Circle())
|
||||
// Text(message.body ?? "--NO BODY?--")
|
||||
// .padding(.all, 8)
|
||||
// .background(Color.Main.backgroundLight)
|
||||
// .clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
// .foregroundColor(Color.Main.black)
|
||||
// } else {
|
||||
// Text(message.body ?? "--NO BODY?--")
|
||||
// .padding(.all, 8)
|
||||
// .background(Color.Main.backgroundLight)
|
||||
// .clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
// .foregroundColor(Color.Main.black)
|
||||
// Image(systemName: "person.fill")
|
||||
// .foregroundColor(Color.Main.black)
|
||||
// .frame(width: 32, height: 32)
|
||||
// .background(Color.Main.backgroundLight)
|
||||
// .clipShape(Circle())
|
||||
// HStack
|
||||
// }
|
||||
// // if isIncoming() {
|
||||
// // Image(systemName: "person.fill")
|
||||
// // .foregroundColor(Color.Main.black)
|
||||
// // .frame(width: 32, height: 32)
|
||||
// // .background(Color.Main.backgroundLight)
|
||||
// // .clipShape(Circle())
|
||||
// // Text(message.body ?? "...")
|
||||
// // .padding(.all, 8)
|
||||
// // .background(Color.Main.backgroundLight)
|
||||
// // .clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
// // .foregroundColor(Color.Main.black)
|
||||
// // } else {
|
||||
// // Text(message.body ?? "--NO BODY?--")
|
||||
// // .padding(.all, 8)
|
||||
// // .background(Color.Main.backgroundLight)
|
||||
// // .clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
// // .foregroundColor(Color.Main.black)
|
||||
// // Image(systemName: "person.fill")
|
||||
// // .foregroundColor(Color.Main.black)
|
||||
// // .frame(width: 32, height: 32)
|
||||
// // .background(Color.Main.backgroundLight)
|
||||
// // .clipShape(Circle())
|
||||
// // }
|
||||
// }
|
||||
// .padding(.horizontal, 16)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.background(Color.red)
|
||||
.sharedListRow()
|
||||
}
|
||||
|
||||
// private func isIncoming() -> Bool {
|
||||
// message.fromJid != state.currentChat?.account
|
||||
// }
|
||||
private func isIncoming() -> Bool {
|
||||
message.from != store.state.conversationsState.currentChat?.account
|
||||
}
|
||||
}
|
||||
|
||||
// for test
|
||||
|
|
Loading…
Reference in a new issue