initial commit

This commit is contained in:
Filip
2026-03-11 16:06:00 +01:00
parent b3c69053f6
commit 5fd80e6dd6
127 changed files with 39684 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
import Foundation
import SwiftUI
@Observable
final class ChatViewModel {
var messages: [Message] = []
var isLoading = false
var isSending = false
var errorMessage: String?
var searchQuery = ""
var searchResults: [String] = [] // message IDs matching search
var currentSearchIndex = 0
private var notificationTask: Task<Void, Never>?
func loadMessages(convId: String, chatClient: ChatClient) async {
isLoading = true
messages = await chatClient.getMessages(convId: convId, limit: 50)
isLoading = false
// Mark as read
let unreadIds = messages.filter { !$0.isMine(currentUserId: await chatClient.userId ?? "") }.map(\.id)
if !unreadIds.isEmpty {
await chatClient.markRead(convId: convId, messageIds: unreadIds)
}
}
func loadOlderMessages(convId: String, chatClient: ChatClient) async {
let older = await chatClient.getMessages(convId: convId, limit: 50, offset: messages.count)
messages.insert(contentsOf: older, at: 0)
}
func sendMessage(convId: String, text: String, members: [ConversationMember],
chatClient: ChatClient, replyTo: String? = nil) async {
guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
isSending = true
errorMessage = nil
let (success, msg) = await chatClient.sendMessage(
convId: convId, text: text, members: members, replyTo: replyTo
)
isSending = false
if !success {
errorMessage = msg
} else {
// Reload messages to get the sent message
await loadMessages(convId: convId, chatClient: chatClient)
}
}
func deleteMessage(messageId: String, convId: String, chatClient: ChatClient) async {
let success = await chatClient.deleteMessage(messageId: messageId, convId: convId)
if success {
messages.removeAll { $0.id == messageId }
}
}
func search(query: String) {
searchQuery = query
if query.isEmpty {
searchResults = []
currentSearchIndex = 0
return
}
let lower = query.lowercased()
searchResults = messages.filter { $0.text?.lowercased().contains(lower) == true }.map(\.id)
currentSearchIndex = searchResults.isEmpty ? 0 : searchResults.count - 1
}
func nextSearchResult() {
guard !searchResults.isEmpty else { return }
currentSearchIndex = (currentSearchIndex + 1) % searchResults.count
}
func prevSearchResult() {
guard !searchResults.isEmpty else { return }
currentSearchIndex = (currentSearchIndex - 1 + searchResults.count) % searchResults.count
}
func startNotificationListener(convId: String, chatClient: ChatClient) {
notificationTask?.cancel()
notificationTask = Task {
for await notification in await chatClient.notifications {
await handleNotification(notification, convId: convId, chatClient: chatClient)
}
}
}
@MainActor
private func handleNotification(_ notification: ChatNotification, convId: String, chatClient: ChatClient) {
switch notification {
case .newMessage(let data):
if data["conversation_id"] as? String == convId {
if let msg = Task.detached(priority: .userInitiated, operation: {
await chatClient.decryptNotification(data)
}) as? Task<Message?, Never> {
Task {
if let message = await msg.value {
messages.append(message)
// Mark as read immediately since we're viewing this conv
await chatClient.markRead(convId: convId, messageIds: [message.id])
}
}
}
}
case .messageDeleted(let data):
if let msgId = data["message_id"] as? String {
messages.removeAll { $0.id == msgId }
}
case .messagesRead(let data):
if let readUserId = data["user_id"] as? String,
let msgIds = data["message_ids"] as? [String] {
for i in messages.indices {
if msgIds.contains(messages[i].id) {
messages[i].readBy.insert(readUserId)
}
}
}
default:
break
}
}
func stop() {
notificationTask?.cancel()
notificationTask = nil
}
}