initial commit
This commit is contained in:
131
ios_client/EncryptedChat/ViewModels/ChatViewModel.swift
Normal file
131
ios_client/EncryptedChat/ViewModels/ChatViewModel.swift
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user