mv-experiment #1
|
@ -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 {
|
extension Client {
|
||||||
static func tryLogin(with credentials: Credentials) async throws -> Client {
|
static func tryLogin(with credentials: Credentials) async throws -> Client {
|
||||||
let client = Client(credentials: credentials)
|
let client = Client(credentials: credentials)
|
||||||
|
|
|
@ -5,7 +5,8 @@ import GRDB
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class ConversationStore: ObservableObject {
|
final class ConversationStore: ObservableObject {
|
||||||
@Published private(set) var messages: Deque<Message> = []
|
@Published private(set) var messages: [Message] = []
|
||||||
|
@Published var replyText = ""
|
||||||
private(set) var roster: Roster
|
private(set) var roster: Roster
|
||||||
|
|
||||||
private let client: Client
|
private let client: Client
|
||||||
|
@ -17,91 +18,50 @@ final class ConversationStore: ObservableObject {
|
||||||
init(roster: Roster, client: Client) {
|
init(roster: Roster, client: Client) {
|
||||||
self.client = client
|
self.client = client
|
||||||
self.roster = roster
|
self.roster = roster
|
||||||
|
subscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConversationStore {
|
extension ConversationStore {
|
||||||
func loadMoreBackward() async {
|
func sendMessage(_ message: String) async {
|
||||||
let earliestDate = messages.last?.date ?? Date()
|
// prepare message
|
||||||
resubscribe(.before(earliestDate))
|
let message = Message(
|
||||||
// let fetchedMessages = await fetchBlock(earliestDate, nil)
|
id: UUID().uuidString,
|
||||||
// messages.append(contentsOf: fetchedMessages)
|
type: .chat,
|
||||||
// if messages.count > messagesMax {
|
date: Date(),
|
||||||
// messages.removeFirst(messages.count - messagesMax)
|
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 }
|
// store as pending on db, and send
|
||||||
// let messages = await fetchBlock(lastMessage.date, nil)
|
do {
|
||||||
// self.messages.append(contentsOf: messages)
|
try await message.save()
|
||||||
}
|
await client.sendMessage(message)
|
||||||
|
} catch {}
|
||||||
func loadMoreForward() async {
|
|
||||||
// guard let firstMessage = messages.first else { return }
|
|
||||||
// let messages = await fetchBlock(nil, firstMessage.date)
|
|
||||||
// self.messages.insert(contentsOf: messages, at: 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ConversationStore {
|
private extension ConversationStore {
|
||||||
enum FetchDirection {
|
func subscribe() {
|
||||||
case before(Date)
|
|
||||||
case after(Date)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resubscribe(_ side: FetchDirection) {
|
|
||||||
switch side {
|
|
||||||
case .before(let date):
|
|
||||||
messagesCancellable = ValueObservation.tracking(Message
|
messagesCancellable = ValueObservation.tracking(Message
|
||||||
.filter(
|
.filter(
|
||||||
(Column("to") == roster.bareJid && Column("from") == roster.contactBareJid) ||
|
(Column("to") == roster.bareJid && Column("from") == roster.contactBareJid) ||
|
||||||
(Column("from") == roster.bareJid && Column("to") == roster.contactBareJid)
|
(Column("from") == roster.bareJid && Column("to") == roster.contactBareJid)
|
||||||
)
|
)
|
||||||
.filter(Column("date") < date)
|
|
||||||
.limit(blockSize)
|
|
||||||
.order(Column("date").desc)
|
.order(Column("date").desc)
|
||||||
.fetchAll
|
.fetchAll
|
||||||
)
|
)
|
||||||
.publisher(in: Database.shared.dbQueue, scheduling: .immediate)
|
.publisher(in: Database.shared.dbQueue, scheduling: .immediate)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { _ in
|
.sink { _ in
|
||||||
} receiveValue: { [weak self] messages in
|
} receiveValue: { [weak self] messages in
|
||||||
self?.processFetched(messages, side)
|
self?.messages = messages
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import SwiftUI
|
||||||
|
|
||||||
struct ConversationScreen: View {
|
struct ConversationScreen: View {
|
||||||
@Environment(\.router) var router
|
@Environment(\.router) var router
|
||||||
@State var conversation: ConversationStore
|
@StateObject var conversation: ConversationStore
|
||||||
|
|
||||||
@State private var autoScroll = true
|
@State private var autoScroll = true
|
||||||
@State private var firstIsVisible = true
|
@State private var firstIsVisible = true
|
||||||
|
@ -100,100 +100,7 @@ struct ConversationScreen: View {
|
||||||
.environmentObject(conversation)
|
.environmentObject(conversation)
|
||||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||||
ConversationTextInput(autoScroll: $autoScroll)
|
ConversationTextInput(autoScroll: $autoScroll)
|
||||||
}
|
.environmentObject(conversation)
|
||||||
.task {
|
|
||||||
await conversation.loadMoreBackward()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct ConversationTextInput: View {
|
struct ConversationTextInput: View {
|
||||||
|
@EnvironmentObject var conversation: ConversationStore
|
||||||
|
|
||||||
@State private var messageStr = ""
|
@State private var messageStr = ""
|
||||||
@FocusState private var isFocused: Bool
|
@FocusState private var isFocused: Bool
|
||||||
@Binding var autoScroll: Bool
|
@Binding var autoScroll: Bool
|
||||||
|
@ -12,10 +14,10 @@ struct ConversationTextInput: View {
|
||||||
.foregroundColor(.Material.Shape.separator)
|
.foregroundColor(.Material.Shape.separator)
|
||||||
.frame(height: 0.5)
|
.frame(height: 0.5)
|
||||||
.padding(.bottom, 8)
|
.padding(.bottom, 8)
|
||||||
if !replyText.isEmpty {
|
if !conversation.replyText.isEmpty {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
Text(replyText)
|
Text(conversation.replyText)
|
||||||
.font(.body3)
|
.font(.body3)
|
||||||
.foregroundColor(Color.Material.Text.main)
|
.foregroundColor(Color.Material.Text.main)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
|
@ -27,7 +29,7 @@ struct ConversationTextInput: View {
|
||||||
.foregroundColor(.Material.Elements.active)
|
.foregroundColor(.Material.Elements.active)
|
||||||
.padding(.leading, 8)
|
.padding(.leading, 8)
|
||||||
.tappablePadding(.symmetric(8)) {
|
.tappablePadding(.symmetric(8)) {
|
||||||
// store.dispatch(.conversationAction(.setReplyText("")))
|
conversation.replyText = ""
|
||||||
}
|
}
|
||||||
.padding(8)
|
.padding(8)
|
||||||
}
|
}
|
||||||
|
@ -66,45 +68,28 @@ struct ConversationTextInput: View {
|
||||||
.padding(.trailing, 8)
|
.padding(.trailing, 8)
|
||||||
.tappablePadding(.symmetric(8)) {
|
.tappablePadding(.symmetric(8)) {
|
||||||
if !messageStr.isEmpty {
|
if !messageStr.isEmpty {
|
||||||
// guard let acc = store.state.conversationsState.currentChat?.account else { return }
|
Task(priority: .userInitiated) {
|
||||||
// guard let contact = store.state.conversationsState.currentChat?.participant else { return }
|
await conversation.sendMessage(composedMessage)
|
||||||
// store.dispatch(.conversationAction(.sendMessage(
|
messageStr = ""
|
||||||
// from: acc,
|
autoScroll = true
|
||||||
// to: contact,
|
}
|
||||||
// body: composedMessage
|
|
||||||
// )))
|
|
||||||
// messageStr = ""
|
|
||||||
// // UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
|
||||||
// store.dispatch(.conversationAction(.setReplyText("")))
|
|
||||||
// autoScroll = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.bottom, 8)
|
.padding(.bottom, 8)
|
||||||
.background(Color.Material.Background.dark)
|
.background(Color.Material.Background.dark)
|
||||||
// .onChange(of: store.state.conversationsState.replyText) { new in
|
.onChange(of: conversation.replyText) { new in
|
||||||
// if !new.isEmpty {
|
if !new.isEmpty {
|
||||||
// isFocused = true
|
isFocused = true
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
// .fullScreenCover(isPresented: Binding<Bool>(
|
|
||||||
// get: { store.state.sharingState.sharingShown },
|
|
||||||
// set: { _ in }
|
|
||||||
// )) {
|
|
||||||
// AttachmentPickerScreen()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var replyText: String {
|
|
||||||
""
|
|
||||||
// store.state.conversationsState.replyText
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var composedMessage: String {
|
private var composedMessage: String {
|
||||||
var result = ""
|
var result = ""
|
||||||
if !replyText.isEmpty {
|
if !conversation.replyText.isEmpty {
|
||||||
result += replyText + "\n\n"
|
result += conversation.replyText + "\n\n"
|
||||||
}
|
}
|
||||||
result += messageStr
|
result += messageStr
|
||||||
return result
|
return result
|
||||||
|
|
Loading…
Reference in a new issue