From 05c23f8fd91f843ad98543a70067919eb40ca8d0 Mon Sep 17 00:00:00 2001 From: fmodf Date: Wed, 14 Aug 2024 11:29:51 +0200 Subject: [PATCH] wip --- .../Client/Client+MartinMessages.swift | 77 ++++++++ .../AppData/Client/Client.swift | 2 + ConversationsClassic/AppData/Model/Chat.swift | 15 ++ .../AppData/Model/Message.swift | 54 ------ .../AppData/Store/ClientStoreError.swift | 1 + .../AppData/Store/ClientsStore.swift | 13 ++ .../Resources/Strings/Localizable.strings | 6 +- .../Main/ChatList/ChatsCreateScreenMain.swift | 183 ++++++++++++++++++ .../View/Main/ChatList/ChatsListScreen.swift | 55 +++--- 9 files changed, 324 insertions(+), 82 deletions(-) create mode 100644 ConversationsClassic/AppData/Client/Client+MartinMessages.swift create mode 100644 ConversationsClassic/View/Main/ChatList/ChatsCreateScreenMain.swift diff --git a/ConversationsClassic/AppData/Client/Client+MartinMessages.swift b/ConversationsClassic/AppData/Client/Client+MartinMessages.swift new file mode 100644 index 0000000..effaf1d --- /dev/null +++ b/ConversationsClassic/AppData/Client/Client+MartinMessages.swift @@ -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 +// } +// } diff --git a/ConversationsClassic/AppData/Client/Client.swift b/ConversationsClassic/AppData/Client/Client.swift index d57eae5..d828256 100644 --- a/ConversationsClassic/AppData/Client/Client.swift +++ b/ConversationsClassic/AppData/Client/Client.swift @@ -24,11 +24,13 @@ final class Client: ObservableObject { private var rosterManager = ClientMartinRosterManager() private var chatsManager = ClientMartinChatsManager() + private var messageManager: ClientMartinMessagesManager init(credentials: Credentials) { self.credentials = credentials state = credentials.isActive ? .enabled(.disconnected) : .disabled connection = Self.prepareConnection(credentials, rosterManager, chatsManager) + messageManager = ClientMartinMessagesManager(connection) connectionCancellable = connection.$state .sink { [weak self] state in guard let self = self else { return } diff --git a/ConversationsClassic/AppData/Model/Chat.swift b/ConversationsClassic/AppData/Model/Chat.swift index 58fc273..3b87934 100644 --- a/ConversationsClassic/AppData/Model/Chat.swift +++ b/ConversationsClassic/AppData/Model/Chat.swift @@ -17,3 +17,18 @@ struct Chat: DBStorable { } 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 + } + } +} diff --git a/ConversationsClassic/AppData/Model/Message.swift b/ConversationsClassic/AppData/Model/Message.swift index 74ba173..8d4dcfd 100644 --- a/ConversationsClassic/AppData/Model/Message.swift +++ b/ConversationsClassic/AppData/Model/Message.swift @@ -36,57 +36,3 @@ struct Message: DBStorable, Equatable { let thread: 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 - } -} diff --git a/ConversationsClassic/AppData/Store/ClientStoreError.swift b/ConversationsClassic/AppData/Store/ClientStoreError.swift index 4ab57bb..7bb2871 100644 --- a/ConversationsClassic/AppData/Store/ClientStoreError.swift +++ b/ConversationsClassic/AppData/Store/ClientStoreError.swift @@ -1,3 +1,4 @@ enum ClientStoreError: Error { case clientNotFound + case rosterNotFound } diff --git a/ConversationsClassic/AppData/Store/ClientsStore.swift b/ConversationsClassic/AppData/Store/ClientsStore.swift index 178663b..e39118b 100644 --- a/ConversationsClassic/AppData/Store/ClientsStore.swift +++ b/ConversationsClassic/AppData/Store/ClientsStore.swift @@ -152,4 +152,17 @@ extension ClientsStore { 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) + } } diff --git a/ConversationsClassic/Resources/Strings/Localizable.strings b/ConversationsClassic/Resources/Strings/Localizable.strings index 14a3c1b..555f755 100644 --- a/ConversationsClassic/Resources/Strings/Localizable.strings +++ b/ConversationsClassic/Resources/Strings/Localizable.strings @@ -43,6 +43,7 @@ // MARK: Chats list screen "ChatsList.title" = "Chats"; +"Chats.Create.Main.title" = "Create"; // MARK: Conversation "Conversation.title" = "Conversation"; @@ -52,9 +53,12 @@ + + + + //"Chat.textfieldPrompt" = "Type a message"; -//"Chats.Create.Main.title" = "Create"; //"Chats.Create.Main.createGroup" = "Create public group"; //"Chats.Create.Main.createPrivateGroup" = "Create private group"; //"Chats.Create.Main.findGroup" = "Find public group"; diff --git a/ConversationsClassic/View/Main/ChatList/ChatsCreateScreenMain.swift b/ConversationsClassic/View/Main/ChatList/ChatsCreateScreenMain.swift new file mode 100644 index 0000000..e82ff73 --- /dev/null +++ b/ConversationsClassic/View/Main/ChatList/ChatsCreateScreenMain.swift @@ -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 diff --git a/ConversationsClassic/View/Main/ChatList/ChatsListScreen.swift b/ConversationsClassic/View/Main/ChatList/ChatsListScreen.swift index 9f936b2..4533f6c 100644 --- a/ConversationsClassic/View/Main/ChatList/ChatsListScreen.swift +++ b/ConversationsClassic/View/Main/ChatList/ChatsListScreen.swift @@ -1,6 +1,7 @@ import SwiftUI struct ChatsListScreen: View { + @Environment(\.router) var router @EnvironmentObject var clientsStore: ClientsStore var body: some View { @@ -17,7 +18,9 @@ struct ChatsListScreen: View { rightButton: .init( image: Image(systemName: "square.and.pencil"), 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 { SharedListRow(iconType: .charCircle(chat.participant), text: chat.participant) .onTapGesture { - // Task { - // router.showModal { - // LoadingScreen() - // } - // defer { - // router.dismissModal() - // } - // - // do { - // let conversation = try await clientsStore.conversationStore(for: roster) - // router.showScreen(.push) { _ in - // ConversationScreen(conversation: conversation) - // } - // } catch { - // router.showAlert( - // .alert, - // title: L10n.Global.Error.title, - // subtitle: L10n.Conversation.startError - // ) { - // Button(L10n.Global.ok, role: .cancel) {} - // } - // } - // } + Task { + router.showModal { + LoadingScreen() + } + defer { + router.dismissModal() + } + + do { + let conversation = try await clientsStore.conversationStore(for: chat) + router.showScreen(.push) { _ in + ConversationScreen(conversation: conversation) + .navigationBarHidden(true) + } + } catch { + router.showAlert( + .alert, + title: L10n.Global.Error.title, + subtitle: L10n.Conversation.startError + ) { + Button(L10n.Global.ok, role: .cancel) {} + } + } + } } } }