mv-experiment #1
|
@ -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
|
||||
// }
|
||||
// }
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
enum ClientStoreError: Error {
|
||||
case clientNotFound
|
||||
case rosterNotFound
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue