diff --git a/ConversationsClassic/AppData/Client/Client.swift b/ConversationsClassic/AppData/Client/Client.swift index d828256..50fabb8 100644 --- a/ConversationsClassic/AppData/Client/Client.swift +++ b/ConversationsClassic/AppData/Client/Client.swift @@ -86,6 +86,31 @@ extension Client { } } +extension Client { + func sendMessage(_ message: Message) async { + guard let to = message.to else { + return + } + guard let chat = connection.module(MessageModule.self).chatManager.chat(for: connection.context, with: BareJID(to)) else { + return + } + + let msg = chat.createMessage(text: message.body ?? "??", id: message.id) + do { + try await chat.send(message: msg) + } catch { + print("Error sending message: \(error)") + } + } + // func sendMessage(_ message: String, to roster: Roster) async { + // guard let chat = chatsManager.chat(for: connection.context, with: BareJID(roster.contactBareJid)) else { + // return + // } + // let message = chat.createMessage(text: message, id: UUID().uuidString) + // try? await chat.send(message: message) + // } +} + extension Client { static func tryLogin(with credentials: Credentials) async throws -> Client { let client = Client(credentials: credentials) diff --git a/ConversationsClassic/AppData/Store/ConversationStore.swift b/ConversationsClassic/AppData/Store/ConversationStore.swift index dac3f9c..4d6d1da 100644 --- a/ConversationsClassic/AppData/Store/ConversationStore.swift +++ b/ConversationsClassic/AppData/Store/ConversationStore.swift @@ -5,7 +5,8 @@ import GRDB @MainActor final class ConversationStore: ObservableObject { - @Published private(set) var messages: Deque = [] + @Published private(set) var messages: [Message] = [] + @Published var replyText = "" private(set) var roster: Roster private let client: Client @@ -17,91 +18,50 @@ final class ConversationStore: ObservableObject { init(roster: Roster, client: Client) { self.client = client self.roster = roster + subscribe() } } extension ConversationStore { - func loadMoreBackward() async { - let earliestDate = messages.last?.date ?? Date() - resubscribe(.before(earliestDate)) - // let fetchedMessages = await fetchBlock(earliestDate, nil) - // messages.append(contentsOf: fetchedMessages) - // if messages.count > messagesMax { - // messages.removeFirst(messages.count - messagesMax) - // } + func sendMessage(_ message: String) async { + // prepare message + let message = Message( + id: UUID().uuidString, + type: .chat, + date: Date(), + contentType: .text, + status: .pending, + from: roster.bareJid, + to: roster.contactBareJid, + body: message, + subject: nil, + thread: nil, + oobUrl: nil + ) - // guard let lastMessage = messages.last else { return } - // let messages = await fetchBlock(lastMessage.date, nil) - // self.messages.append(contentsOf: messages) - } - - func loadMoreForward() async { - // guard let firstMessage = messages.first else { return } - // let messages = await fetchBlock(nil, firstMessage.date) - // self.messages.insert(contentsOf: messages, at: 0) + // store as pending on db, and send + do { + try await message.save() + await client.sendMessage(message) + } catch {} } } private extension ConversationStore { - enum FetchDirection { - case before(Date) - case after(Date) - } - - func resubscribe(_ side: FetchDirection) { - switch side { - case .before(let date): - messagesCancellable = ValueObservation.tracking(Message - .filter( - (Column("to") == roster.bareJid && Column("from") == roster.contactBareJid) || - (Column("from") == roster.bareJid && Column("to") == roster.contactBareJid) - ) - .filter(Column("date") < date) - .limit(blockSize) - .order(Column("date").desc) - .fetchAll + func subscribe() { + messagesCancellable = ValueObservation.tracking(Message + .filter( + (Column("to") == roster.bareJid && Column("from") == roster.contactBareJid) || + (Column("from") == roster.bareJid && Column("to") == roster.contactBareJid) ) - .publisher(in: Database.shared.dbQueue, scheduling: .immediate) - .sink { _ in - } receiveValue: { [weak self] messages in - self?.processFetched(messages, side) - } - - case .after(let date): - messagesCancellable = ValueObservation.tracking(Message - .filter( - (Column("to") == roster.bareJid && Column("from") == roster.contactBareJid) || - (Column("from") == roster.bareJid && Column("to") == roster.contactBareJid) - ) - .filter(Column("date") > date) - .limit(blockSize) - .order(Column("date").desc) - .fetchAll - ) - .publisher(in: Database.shared.dbQueue, scheduling: .immediate) - .sink { _ in - } receiveValue: { [weak self] messages in - self?.processFetched(messages, side) - } + .order(Column("date").desc) + .fetchAll + ) + .publisher(in: Database.shared.dbQueue, scheduling: .immediate) + .receive(on: DispatchQueue.main) + .sink { _ in + } receiveValue: { [weak self] messages in + self?.messages = messages } } - - func processFetched(_ messages: [Message], _ side: FetchDirection) { - switch side { - case .before: - self.messages.append(contentsOf: messages) - - case .after: - self.messages.insert(contentsOf: messages, at: 0) - } - - Task { - await processAttachments(messages) - } - } - - func processAttachments(_ messages: [Message]) async { - // load attachment here - print(messages.count) - } } diff --git a/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift b/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift index 12e71ec..88cb70b 100644 --- a/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift +++ b/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift @@ -5,7 +5,7 @@ import SwiftUI struct ConversationScreen: View { @Environment(\.router) var router - @State var conversation: ConversationStore + @StateObject var conversation: ConversationStore @State private var autoScroll = true @State private var firstIsVisible = true @@ -100,100 +100,7 @@ struct ConversationScreen: View { .environmentObject(conversation) .safeAreaInset(edge: .bottom, spacing: 0) { ConversationTextInput(autoScroll: $autoScroll) - } - .task { - await conversation.loadMoreBackward() + .environmentObject(conversation) } } } - -// Preview -// #if DEBUG -// struct ConversationScreen_Previews: PreviewProvider { -// static var previews: some View { -// ConversationScreen() -// .environmentObject(pStore) -// } -// -// static var pStore: AppStore { -// let state = pState -// return AppStore(initialState: state, reducer: AppState.reducer, middlewares: []) -// } -// -// static var pState: AppState { -// var state = AppState() -// -// let acc = "user@test.com" -// let contact = "some@test.com" -// -// state.conversationsState.currentChat = Chat(id: "1", account: acc, participant: contact, type: .chat) -// state.conversationsState.currentMessages = [ -// Message( -// id: "1", -// type: .chat, -// contentType: .text, -// from: contact, -// to: acc, -// body: "this is for test sdgdsfg dsfg dsfgdg dsfgdfgsdgsdfgdfg sdfgdsfgdfsg dsfgdsfgsdfg dsfgdfgsdg fgf fgfg sdfsdf sdfsdf sdf sdfsdf sdf sdfsdf sdfsdfsdf sdfsdf ", -// subject: nil, -// thread: nil, -// oobUrl: nil, -// date: Date(), -// pending: true, sentError: false -// ), -// Message( -// id: "2", -// type: .chat, -// contentType: .text, -// from: contact, -// to: acc, -// body: "this is for testsdfsdf sdfsdf sdfs sdf sdffsdf sdf sdf sdf sdf sdf sdff sdfffwwe ", -// subject: nil, -// thread: nil, -// oobUrl: nil, -// date: Date(), -// pending: false, -// sentError: false -// ), -// Message(id: "3", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: true), -// Message( -// id: "4", -// type: .chat, -// contentType: .text, -// from: acc, -// to: contact, -// body: "this is for test sdfkjwek jwkjfh jwerf jdfhskjdhf jsdhfjhwefh sjdhfh fsdjhfh sd ", -// subject: nil, -// thread: nil, -// oobUrl: nil, -// date: Date(), -// pending: false, -// sentError: false -// ), -// Message(id: "5", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for test sdfjkkeke kekkddjw;; w;edkdjfj l kjwekrjfk wef", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), -// Message(id: "6", type: .chat, contentType: .text, from: acc, to: contact, body: "this is for testsdf dsdkkekkddn wejkjfj ", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), -// Message( -// id: "7", -// type: .chat, -// contentType: .text, -// from: acc, -// to: contact, -// -// body: "this is for test sdgdsfg dsfg dsfgdg dsfgdfgsdgsdfgdfg sdfgdsfgdfsg dsfgdsfgsdfg dsfgdfgsdg fgf fgfg sdfsdf sdfsdf sdf sdfsdf sdf sdfsdf sdfsdfsdf sdfsdf ", -// subject: nil, -// thread: nil, -// oobUrl: nil, -// date: Date(), pending: false, sentError: false -// ), -// Message(id: "8", type: .chat, contentType: .text, from: acc, to: contact, body: "so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), -// Message(id: "9", type: .chat, contentType: .text, from: contact, to: acc, body: "so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), -// Message(id: "10", type: .chat, contentType: .text, from: acc, to: contact, body: "so test so test so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), -// Message(id: "11", type: .chat, contentType: .text, from: contact, to: acc, body: "xD", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false) -// ] -// -// state.conversationsState.replyText = "> Some Text here! And if it a long and very long text sdfsadfsadfsafsadfsadfsadfsadfassadfsadfsafsafdsadfsafdsadfsadfas sdf sdf asdf sdfasdfsd sdfasdf sdfsdfdsasdfsdfa dsafsaf" -// -// return state -// } -// } -// #endif diff --git a/ConversationsClassic/View/Main/Conversation/ConversationTextInput.swift b/ConversationsClassic/View/Main/Conversation/ConversationTextInput.swift index b564184..4a477fb 100644 --- a/ConversationsClassic/View/Main/Conversation/ConversationTextInput.swift +++ b/ConversationsClassic/View/Main/Conversation/ConversationTextInput.swift @@ -2,6 +2,8 @@ import SwiftUI import UIKit struct ConversationTextInput: View { + @EnvironmentObject var conversation: ConversationStore + @State private var messageStr = "" @FocusState private var isFocused: Bool @Binding var autoScroll: Bool @@ -12,10 +14,10 @@ struct ConversationTextInput: View { .foregroundColor(.Material.Shape.separator) .frame(height: 0.5) .padding(.bottom, 8) - if !replyText.isEmpty { + if !conversation.replyText.isEmpty { VStack(spacing: 0) { HStack(alignment: .top) { - Text(replyText) + Text(conversation.replyText) .font(.body3) .foregroundColor(Color.Material.Text.main) .multilineTextAlignment(.leading) @@ -27,7 +29,7 @@ struct ConversationTextInput: View { .foregroundColor(.Material.Elements.active) .padding(.leading, 8) .tappablePadding(.symmetric(8)) { - // store.dispatch(.conversationAction(.setReplyText(""))) + conversation.replyText = "" } .padding(8) } @@ -66,45 +68,28 @@ struct ConversationTextInput: View { .padding(.trailing, 8) .tappablePadding(.symmetric(8)) { if !messageStr.isEmpty { - // guard let acc = store.state.conversationsState.currentChat?.account else { return } - // guard let contact = store.state.conversationsState.currentChat?.participant else { return } - // store.dispatch(.conversationAction(.sendMessage( - // from: acc, - // to: contact, - // body: composedMessage - // ))) - // messageStr = "" - // // UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - // store.dispatch(.conversationAction(.setReplyText(""))) - // autoScroll = true + Task(priority: .userInitiated) { + await conversation.sendMessage(composedMessage) + messageStr = "" + autoScroll = true + } } } } } .padding(.bottom, 8) .background(Color.Material.Background.dark) - // .onChange(of: store.state.conversationsState.replyText) { new in - // if !new.isEmpty { - // isFocused = true - // } - // } - // .fullScreenCover(isPresented: Binding( - // get: { store.state.sharingState.sharingShown }, - // set: { _ in } - // )) { - // AttachmentPickerScreen() - // } - } - - private var replyText: String { - "" - // store.state.conversationsState.replyText + .onChange(of: conversation.replyText) { new in + if !new.isEmpty { + isFocused = true + } + } } private var composedMessage: String { var result = "" - if !replyText.isEmpty { - result += replyText + "\n\n" + if !conversation.replyText.isEmpty { + result += conversation.replyText + "\n\n" } result += messageStr return result