128 lines
4.3 KiB
Swift
128 lines
4.3 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
@Observable
|
|
final class ConversationListVM {
|
|
var conversations: [Conversation] = []
|
|
var invitations: [Invitation] = []
|
|
var onlineUsers: Set<String> = []
|
|
var unreadCounts: [String: Int] = [:]
|
|
var favorites: Set<String> = []
|
|
var isLoading = false
|
|
|
|
private var notificationTask: Task<Void, Never>?
|
|
|
|
func load(chatClient: ChatClient, email: String) async {
|
|
isLoading = true
|
|
|
|
// Load favorites from disk
|
|
favorites = KeyStorage.loadFavorites(email: email)
|
|
|
|
// Fetch conversations
|
|
let convs = await chatClient.listConversations()
|
|
conversations = sortConversations(convs, currentUserId: await chatClient.userId ?? "")
|
|
|
|
// Populate unread counts from server
|
|
for conv in conversations where conv.unreadCount > 0 {
|
|
unreadCounts[conv.id] = conv.unreadCount
|
|
}
|
|
|
|
// Fetch invitations
|
|
invitations = await chatClient.listInvitations()
|
|
|
|
isLoading = false
|
|
|
|
// Start notification listener
|
|
startNotificationListener(chatClient: chatClient, email: email)
|
|
}
|
|
|
|
func refresh(chatClient: ChatClient) async {
|
|
let convs = await chatClient.listConversations()
|
|
conversations = sortConversations(convs, currentUserId: await chatClient.userId ?? "")
|
|
invitations = await chatClient.listInvitations()
|
|
}
|
|
|
|
func toggleFavorite(convId: String, email: String) {
|
|
if favorites.contains(convId) {
|
|
favorites.remove(convId)
|
|
} else {
|
|
favorites.insert(convId)
|
|
}
|
|
try? KeyStorage.saveFavorites(email: email, favorites: favorites)
|
|
|
|
// Re-sort
|
|
let userId = conversations.first?.createdBy ?? ""
|
|
conversations = sortConversations(conversations, currentUserId: userId)
|
|
}
|
|
|
|
func markConversationRead(convId: String) {
|
|
unreadCounts[convId] = 0
|
|
}
|
|
|
|
func incrementUnread(convId: String) {
|
|
unreadCounts[convId, default: 0] += 1
|
|
}
|
|
|
|
private func sortConversations(_ convs: [Conversation], currentUserId: String) -> [Conversation] {
|
|
var result = convs.map { conv -> Conversation in
|
|
var c = conv
|
|
c.isFavorite = favorites.contains(conv.id)
|
|
c.unreadCount = unreadCounts[conv.id] ?? conv.unreadCount
|
|
return c
|
|
}
|
|
|
|
result.sort { a, b in
|
|
// Favorites first
|
|
if a.isFavorite != b.isFavorite { return a.isFavorite }
|
|
// Online DMs next
|
|
let aOnline = a.dmPartnerId(currentUserId: currentUserId).map { onlineUsers.contains($0) } ?? false
|
|
let bOnline = b.dmPartnerId(currentUserId: currentUserId).map { onlineUsers.contains($0) } ?? false
|
|
if aOnline != bOnline { return aOnline }
|
|
// Alphabetical
|
|
return a.displayName(currentUserId: currentUserId).lowercased() < b.displayName(currentUserId: currentUserId).lowercased()
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
private func startNotificationListener(chatClient: ChatClient, email: String) {
|
|
notificationTask?.cancel()
|
|
notificationTask = Task {
|
|
for await notification in await chatClient.notifications {
|
|
await handleNotification(notification, chatClient: chatClient, email: email)
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private func handleNotification(_ notification: ChatNotification, chatClient: ChatClient, email: String) {
|
|
switch notification {
|
|
case .newMessage(let data):
|
|
if let convId = data["conversation_id"] as? String {
|
|
incrementUnread(convId: convId)
|
|
}
|
|
case .onlineUsers(let userIds):
|
|
onlineUsers = Set(userIds)
|
|
case .userOnline(let userId):
|
|
onlineUsers.insert(userId)
|
|
case .userOffline(let userId):
|
|
onlineUsers.remove(userId)
|
|
case .conversationCreated, .memberAdded, .memberRemoved, .conversationRenamed:
|
|
Task { await refresh(chatClient: chatClient) }
|
|
case .groupInvitation:
|
|
Task { invitations = await chatClient.listInvitations() }
|
|
case .connectionStateChanged(let connected):
|
|
if !connected {
|
|
// Could trigger auto-reconnect here
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
func stop() {
|
|
notificationTask?.cancel()
|
|
notificationTask = nil
|
|
}
|
|
}
|