This commit is contained in:
fmodf 2024-08-14 11:29:51 +02:00
parent c47b6e9c21
commit 05c23f8fd9
9 changed files with 324 additions and 82 deletions

View file

@ -0,0 +1,77 @@
import Combine
import GRDB
import Martin
final class ClientMartinMessagesManager {
private var cancellable: AnyCancellable?
init(_ xmppConnection: XMPPClient) {
cancellable = xmppConnection.module(MessageModule.self).messagesPublisher
.sink { [weak self] message in
self?.handleClientMessage(message)
}
}
private func handleClientMessage(_ martinMessage: MessageModule.MessageReceived) {
#if DEBUG
print("---")
print("Message received: \(martinMessage)")
print("---")
#endif
}
}
//
// extension Message {
// static func map(from martinMessage: Martin.Message) -> Message? {
// #if DEBUG
// print("---")
// print("Message received: \(martinMessage)")
// print("---")
// #endif
//
// // 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
//
// // Extract date or set current
// var date = Date()
// if let timestampStr = martinMessage.attribute("archived_date"), let timeInterval = TimeInterval(timestampStr) {
// date = Date(timeIntervalSince1970: timeInterval)
// }
//
// // Msg
// let msg = Message(
// id: martinMessage.id ?? UUID().uuidString,
// type: type,
// date: date,
// contentType: contentType,
// status: .sent,
// from: from,
// to: to,
// body: martinMessage.body,
// subject: martinMessage.subject,
// thread: martinMessage.thread,
// oobUrl: martinMessage.oob
// )
// return msg
// }
// }

View file

@ -24,11 +24,13 @@ final class Client: ObservableObject {
private var rosterManager = ClientMartinRosterManager() private var rosterManager = ClientMartinRosterManager()
private var chatsManager = ClientMartinChatsManager() private var chatsManager = ClientMartinChatsManager()
private var messageManager: ClientMartinMessagesManager
init(credentials: Credentials) { init(credentials: Credentials) {
self.credentials = credentials self.credentials = credentials
state = credentials.isActive ? .enabled(.disconnected) : .disabled state = credentials.isActive ? .enabled(.disconnected) : .disabled
connection = Self.prepareConnection(credentials, rosterManager, chatsManager) connection = Self.prepareConnection(credentials, rosterManager, chatsManager)
messageManager = ClientMartinMessagesManager(connection)
connectionCancellable = connection.$state connectionCancellable = connection.$state
.sink { [weak self] state in .sink { [weak self] state in
guard let self = self else { return } guard let self = self else { return }

View file

@ -17,3 +17,18 @@ struct Chat: DBStorable {
} }
extension Chat: Equatable {} extension Chat: Equatable {}
extension Chat {
func fetchRoster() async throws -> Roster {
try await Database.shared.dbQueue.read { db in
guard
let roster = try Roster
.filter(Column("bareJid") == account && Column("contactBareJid") == participant)
.fetchOne(db)
else {
throw ClientStoreError.rosterNotFound
}
return roster
}
}
}

View file

@ -36,57 +36,3 @@ struct Message: DBStorable, Equatable {
let thread: String? let thread: String?
let oobUrl: String? let oobUrl: String?
} }
extension Message {
static func map(from martinMessage: Martin.Message) -> Message? {
#if DEBUG
print("---")
print("Message received: \(martinMessage)")
print("---")
#endif
// 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
// Extract date or set current
var date = Date()
if let timestampStr = martinMessage.attribute("archived_date"), let timeInterval = TimeInterval(timestampStr) {
date = Date(timeIntervalSince1970: timeInterval)
}
// Msg
let msg = Message(
id: martinMessage.id ?? UUID().uuidString,
type: type,
date: date,
contentType: contentType,
status: .sent,
from: from,
to: to,
body: martinMessage.body,
subject: martinMessage.subject,
thread: martinMessage.thread,
oobUrl: martinMessage.oob
)
return msg
}
}

View file

@ -1,3 +1,4 @@
enum ClientStoreError: Error { enum ClientStoreError: Error {
case clientNotFound case clientNotFound
case rosterNotFound
} }

View file

@ -152,4 +152,17 @@ extension ClientsStore {
return ConversationStore(roster: roster, client: client) return ConversationStore(roster: roster, client: client)
} }
func conversationStore(for chat: Chat) async throws -> ConversationStore {
while !ready {
await Task.yield()
}
guard let client = clients.first(where: { $0.credentials.bareJid == chat.account }) else {
throw ClientStoreError.clientNotFound
}
let roster = try await chat.fetchRoster()
return ConversationStore(roster: roster, client: client)
}
} }

View file

@ -43,6 +43,7 @@
// MARK: Chats list screen // MARK: Chats list screen
"ChatsList.title" = "Chats"; "ChatsList.title" = "Chats";
"Chats.Create.Main.title" = "Create";
// MARK: Conversation // MARK: Conversation
"Conversation.title" = "Conversation"; "Conversation.title" = "Conversation";
@ -52,9 +53,12 @@
//"Chat.textfieldPrompt" = "Type a message"; //"Chat.textfieldPrompt" = "Type a message";
//"Chats.Create.Main.title" = "Create";
//"Chats.Create.Main.createGroup" = "Create public group"; //"Chats.Create.Main.createGroup" = "Create public group";
//"Chats.Create.Main.createPrivateGroup" = "Create private group"; //"Chats.Create.Main.createPrivateGroup" = "Create private group";
//"Chats.Create.Main.findGroup" = "Find public group"; //"Chats.Create.Main.findGroup" = "Find public group";

View file

@ -0,0 +1,183 @@
import SwiftUI
struct ChatsCreateScreenMain: View {
@Environment(\.router) var router
var body: some View {
ZStack {
// Background color
Color.Material.Background.light
.ignoresSafeArea()
// Content
VStack(spacing: 0) {
// Header
SharedNavigationBar(
leftButton: .init(
image: Image(systemName: "xmark"),
action: {
router.dismissScreen()
}
),
centerText: .init(text: L10n.Chats.Create.Main.title)
)
// List
List {
Text("test")
// ChatsCreateRowButton(
// title: L10n.Chats.Create.Main.createGroup,
// image: "person.2.fill",
// action: {}
// )
// ChatsCreateRowButton(
// title: L10n.Chats.Create.Main.createPrivateGroup,
// image: "person.2.fill",
// action: {}
// )
// ChatsCreateRowButton(
// title: L10n.Chats.Create.Main.findGroup,
// image: "magnifyingglass",
// action: {}
// )
// for contacts list
// let rosters = store.state.rostersState.rosters.filter { !$0.locallyDeleted }
// if rosters.isEmpty {
// ChatsCreateRowSeparator()
// }
}
.listStyle(.plain)
Spacer()
}
}
}
}
// private struct ChatsCreateRowButton: View {
// var title: String
// var image: String
// var action: () -> Void
//
// var body: some View {
// VStack(alignment: .center, spacing: 0) {
// Spacer()
// HStack(alignment: .center, spacing: 16) {
// Image(systemName: image)
// .font(.head2)
// .foregroundColor(.Material.Elements.active)
// .padding(.leading, 16)
// Text(title)
// .font(.body1)
// .foregroundColor(.Material.Text.main)
// Spacer()
// }
// Spacer()
// Rectangle()
// .frame(maxWidth: .infinity)
// .frame(height: 1)
// .foregroundColor(.Material.Background.dark)
// }
// .sharedListRow()
// .frame(height: 48)
// .onTapGesture {
// action()
// }
// }
// }
private struct ChatsCreateRowSeparator: View {
var body: some View {
Text("aa")
}
}
// import SwiftUI
//
// struct CreateConversationMainScreen: View {
// @EnvironmentObject var store: AppStore
//
// var body: some View {
// ZStack {
// // Background color
// Color.Material.Background.light
// .ignoresSafeArea()
//
// // Content
// VStack(spacing: 0) {
// // Header
// CreateConversationHeader()
//
// // Chats list
// // if !store.state.chatsState.chats.isEmpty {
// // List {
// // ForEach(store.state.chatsState.chats) { chat in
// // ChatsRow(chat: chat)
// // }
// // }
// // .listStyle(.plain)
// // .background(Color.Material.Background.light)
// // } else {
// // Spacer()
// // }
// //
// // // Tab bar
// // SharedTabBar()
// }
// }
// }
// }
//
// private struct CreateConversationHeader: View {
// @EnvironmentObject var store: AppStore
//
// var body: some View {
// ZStack {
// // bg
// Color.Material.Background.dark
// .ignoresSafeArea()
//
// HStack(spacing: 0) {
// Image(systemName: "arrow.left")
// .foregroundColor(.Material.Elements.active)
// .padding(.leading, 16)
// .tappablePadding(.symmetric(12)) {
// store.dispatch(.changeFlow(store.state.previousFlow))
// }
// Spacer()
// }
//
// // title
// Text("New conversation")
// .font(.head2)
// .foregroundColor(Color.Material.Text.main)
// }
// }
// }
//
// Preview
// #if DEBUG
// struct ChatsCreateMainScreen_Previews: PreviewProvider {
// static var previews: some View {
// ChatsCreateMainScreen(isPresented: .constant(true))
// .environmentObject(pStore)
// }
//
// static var pStore: AppStore {
// let state = pState
// return AppStore(initialState: state, reducer: AppState.reducer, middlewares: [])
// }
//
// static var pState: AppState {
// var state = AppState()
//
// state.rostersState.rosters = [
// .init(contactBareJid: "test@me.com", subscription: "both", ask: true, data: .init(groups: [], annotations: []))
// ]
//
// return state
// }
// }
// #endif

View file

@ -1,6 +1,7 @@
import SwiftUI import SwiftUI
struct ChatsListScreen: View { struct ChatsListScreen: View {
@Environment(\.router) var router
@EnvironmentObject var clientsStore: ClientsStore @EnvironmentObject var clientsStore: ClientsStore
var body: some View { var body: some View {
@ -17,7 +18,9 @@ struct ChatsListScreen: View {
rightButton: .init( rightButton: .init(
image: Image(systemName: "square.and.pencil"), image: Image(systemName: "square.and.pencil"),
action: { action: {
// isCretePanelPresented = true router.showScreen(.fullScreenCover) { _ in
ChatsCreateScreenMain()
}
} }
) )
) )
@ -36,9 +39,6 @@ struct ChatsListScreen: View {
} }
} }
} }
// .fullScreenCover(isPresented: $isCretePanelPresented) {
// ChatsCreateMainScreen(isPresented: $isCretePanelPresented)
// }
} }
} }
@ -51,29 +51,30 @@ private struct ChatsRow: View {
var body: some View { var body: some View {
SharedListRow(iconType: .charCircle(chat.participant), text: chat.participant) SharedListRow(iconType: .charCircle(chat.participant), text: chat.participant)
.onTapGesture { .onTapGesture {
// Task { Task {
// router.showModal { router.showModal {
// LoadingScreen() LoadingScreen()
// } }
// defer { defer {
// router.dismissModal() router.dismissModal()
// } }
//
// do { do {
// let conversation = try await clientsStore.conversationStore(for: roster) let conversation = try await clientsStore.conversationStore(for: chat)
// router.showScreen(.push) { _ in router.showScreen(.push) { _ in
// ConversationScreen(conversation: conversation) ConversationScreen(conversation: conversation)
// } .navigationBarHidden(true)
// } catch { }
// router.showAlert( } catch {
// .alert, router.showAlert(
// title: L10n.Global.Error.title, .alert,
// subtitle: L10n.Conversation.startError title: L10n.Global.Error.title,
// ) { subtitle: L10n.Conversation.startError
// Button(L10n.Global.ok, role: .cancel) {} ) {
// } Button(L10n.Global.ok, role: .cancel) {}
// } }
// } }
}
} }
} }
} }