wip
This commit is contained in:
parent
8ce21712b7
commit
9b4323ccd3
|
@ -1,3 +1,4 @@
|
||||||
enum ConversationAction: Codable {
|
enum ConversationAction: Codable {
|
||||||
case makeConversationActive(chat: Chat)
|
case makeConversationActive(chat: Chat)
|
||||||
|
case messageForCurrentConversationReceived(Message)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
enum MessagesAction: Codable {
|
enum MessagesAction: Codable {
|
||||||
case newMessageReceived(Message)
|
case dumb
|
||||||
case messageDraftUpdate(Message)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
enum XMPPAction: Codable {
|
enum XMPPAction: Codable {
|
||||||
case clientConnectionChanged(jid: String, state: ConnectionStatus)
|
case clientConnectionChanged(jid: String, state: ConnectionStatus)
|
||||||
|
case xmppMessageReceived(Message)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,16 +47,14 @@ extension Database {
|
||||||
// messages
|
// messages
|
||||||
try db.create(table: "messages", options: [.ifNotExists]) { table in
|
try db.create(table: "messages", options: [.ifNotExists]) { table in
|
||||||
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
|
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("type", .text).notNull()
|
||||||
// table.column("isReaded", .boolean).notNull().defaults(to: false)
|
table.column("contentType", .text).notNull()
|
||||||
// table.column("subject", .text)
|
table.column("from", .text).notNull()
|
||||||
// table.column("threadId", .text)
|
table.column("to", .text)
|
||||||
// table.column("errorType", .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 {
|
final class ConversationMiddleware {
|
||||||
static let shared = 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 {
|
switch action {
|
||||||
case .chatsAction(.chatStarted(let chat)):
|
case .chatsAction(.chatStarted(let chat)):
|
||||||
return Just(AppAction.conversationAction(.makeConversationActive(chat: chat))).eraseToAnyPublisher()
|
return Just(AppAction.conversationAction(.makeConversationActive(chat: chat))).eraseToAnyPublisher()
|
||||||
|
@ -11,6 +11,17 @@ final class ConversationMiddleware {
|
||||||
case .conversationAction(.makeConversationActive):
|
case .conversationAction(.makeConversationActive):
|
||||||
return Just(AppAction.changeFlow(.conversation)).eraseToAnyPublisher()
|
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:
|
default:
|
||||||
return Empty().eraseToAnyPublisher()
|
return Empty().eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,13 @@ final class DatabaseMiddleware {
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
|
case .xmppAction(.xmppMessageReceived(let message)):
|
||||||
|
if message.type != .chat {
|
||||||
|
return Empty().eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
// TODO: Store msg here!
|
||||||
|
return Empty().eraseToAnyPublisher()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Empty().eraseToAnyPublisher()
|
return Empty().eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,23 +18,11 @@ final class XMPPMiddleware {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
service.clientMessages.sink { client, martinMessage in
|
service.clientMessages.sink { _, martinMessage in
|
||||||
print("---")
|
guard let message = Message.map(martinMessage) else { return }
|
||||||
print("Message received: \(martinMessage)")
|
DispatchQueue.main.async {
|
||||||
print("In client: \(client)")
|
store.dispatch(.xmppAction(.xmppMessageReceived(message)))
|
||||||
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)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,95 +4,73 @@ import Martin
|
||||||
|
|
||||||
enum MessageType: String, Codable, DatabaseValueConvertible {
|
enum MessageType: String, Codable, DatabaseValueConvertible {
|
||||||
case chat
|
case chat
|
||||||
case channel
|
|
||||||
case groupchat
|
case groupchat
|
||||||
|
case error
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MessageContentType: String, Codable, DatabaseValueConvertible {
|
enum MessageContentType: String, Codable, DatabaseValueConvertible {
|
||||||
case text
|
case text
|
||||||
case image
|
|
||||||
case video
|
|
||||||
case audio
|
|
||||||
case file
|
|
||||||
case location
|
|
||||||
case typing
|
case typing
|
||||||
case invite
|
case invite
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MessageContainer: Stateable, DatabaseValueConvertible {
|
struct Message: Stateable, Identifiable, DatabaseValueConvertible {
|
||||||
let id: String
|
let id: String
|
||||||
let type: MessageType
|
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 {
|
extension Message {
|
||||||
var type: MessageContentType { get }
|
// 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
|
||||||
|
|
||||||
//
|
// Check that the message type is supported
|
||||||
// enum MessageType: String, Codable, DatabaseValueConvertible {
|
let chatTypes: [StanzaType] = [.chat, .groupchat]
|
||||||
// case text
|
guard let mType = martinMessage.type, chatTypes.contains(mType) else {
|
||||||
// case image
|
#if DEBUG
|
||||||
// case video
|
print("Unsupported message type: \(martinMessage.type?.rawValue ?? "nil")")
|
||||||
// case audio
|
#endif
|
||||||
// case file
|
return nil
|
||||||
// case location
|
}
|
||||||
// case writingProcessUpdate
|
|
||||||
// }
|
// Type
|
||||||
//
|
let type = MessageType(rawValue: martinMessage.type?.rawValue ?? "") ?? .chat
|
||||||
// struct Message: DBStorable, Equatable {
|
|
||||||
// static let databaseTableName = "messages"
|
// Content type
|
||||||
//
|
var contentType: MessageContentType = .text
|
||||||
// let id: String
|
if martinMessage.hints.contains(.noStore) {
|
||||||
// let chatId: String
|
contentType = .typing
|
||||||
// let fromJid: String
|
}
|
||||||
// let toJid: String
|
|
||||||
// let timestamp: Date
|
// From/To
|
||||||
// let body: String?
|
let from = martinMessage.from?.bareJid.stringValue ?? ""
|
||||||
// let type: MessageType
|
let to = martinMessage.to?.bareJid.stringValue
|
||||||
// }
|
|
||||||
//
|
// Msg
|
||||||
// // Special extnesion for mapping Martin.Message to Message
|
let msg = Message(
|
||||||
// extension Message {
|
id: martinMessage.id ?? UUID().uuidString,
|
||||||
// static func mapMartinMessage(_ martinMessage: Martin.Message) -> Message? {
|
type: type,
|
||||||
// // for draft messages
|
contentType: contentType,
|
||||||
// if martinMessage.hints.contains(.noStore) {
|
from: from,
|
||||||
// return Message(
|
to: to,
|
||||||
// id: martinMessage.id ?? UUID().uuidString,
|
body: martinMessage.body,
|
||||||
// chatId: "none", // chat id will be filled later
|
subject: martinMessage.subject,
|
||||||
// fromJid: martinMessage.from?.bareJid.stringValue ?? "",
|
thread: martinMessage.thread,
|
||||||
// toJid: martinMessage.to?.bareJid.stringValue ?? "",
|
oobUrl: martinMessage.oob
|
||||||
// timestamp: Date(),
|
)
|
||||||
// body: nil,
|
return msg
|
||||||
// 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
|
|
||||||
// // )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -4,6 +4,9 @@ extension ConversationState {
|
||||||
case .makeConversationActive(let chat):
|
case .makeConversationActive(let chat):
|
||||||
state.currentChat = chat
|
state.currentChat = chat
|
||||||
|
|
||||||
|
case .messageForCurrentConversationReceived(let message):
|
||||||
|
state.currentMessages.append(message)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
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
|
@EnvironmentObject var store: AppStore
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
ZStack {
|
||||||
// Header
|
// Background color
|
||||||
ConversationScreenHeader()
|
Color.Main.backgroundLight
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
// Msg list
|
// Content
|
||||||
// if !state.messages.isEmpty {
|
VStack(spacing: 0) {
|
||||||
// List {
|
// Header
|
||||||
// ForEach(state.messages) { message in
|
ConversationScreenHeader()
|
||||||
// ChatMessageView(message: message)
|
|
||||||
// }
|
// Msg list
|
||||||
// }
|
let messages = store.state.conversationsState.currentMessages
|
||||||
// } else {
|
if !messages.isEmpty {
|
||||||
// Text("No messages")
|
List {
|
||||||
// Spacer()
|
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 {
|
private struct ConversationMessageRow: View {
|
||||||
// @EnvironmentObject var state: AppState
|
@EnvironmentObject var store: AppStore
|
||||||
|
|
||||||
let message: Message
|
let message: Message
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
VStack {
|
||||||
Text(message.body ?? "--NO BODY?--")
|
if isIncoming() {
|
||||||
// .padding(.all, 8)
|
HStack {
|
||||||
// .background(.black)
|
MessageContainer(message: message, isOutgoing: false)
|
||||||
// .clipShape(RoundedRectangle(cornerRadius: 8))
|
.padding(.all, 8)
|
||||||
.foregroundColor(Color.Main.black)
|
Spacer()
|
||||||
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() {
|
// if isIncoming() {
|
||||||
// Image(systemName: "person.fill")
|
// HStack
|
||||||
// .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())
|
|
||||||
// }
|
// }
|
||||||
|
// // 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)
|
.sharedListRow()
|
||||||
.background(Color.red)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private func isIncoming() -> Bool {
|
private func isIncoming() -> Bool {
|
||||||
// message.fromJid != state.currentChat?.account
|
message.from != store.state.conversationsState.currentChat?.account
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for test
|
|
||||||
|
|
Loading…
Reference in a new issue