Private
Public Access
1
0
Files
Kordophone/osx/kordophone2/ConversationListView.swift
2025-09-10 14:41:24 -07:00

124 lines
3.8 KiB
Swift

//
// ConversationListView.swift
// kordophone2
//
// Created by James Magahern on 8/24/25.
//
import SwiftUI
struct ConversationListView: View
{
@Binding var model: ViewModel
@Environment(\.xpcClient) private var xpcClient
var body: some View {
List($model.conversations, selection: $model.selectedConversation) { conv in
let isUnread = conv.wrappedValue.unreadCount > 0
HStack(spacing: 0.0) {
if isUnread {
Image(systemName: "circlebadge.fill")
.foregroundStyle(.tint)
.frame(width: 10.0)
} else {
Rectangle()
.foregroundStyle(.clear)
.frame(width: 10.0)
}
VStack(alignment: .leading) {
Text(conv.wrappedValue.displayName)
.bold()
Text(conv.wrappedValue.messagePreview)
.foregroundStyle(.secondary)
}
.padding(8.0)
}
.id(conv.id)
}
.listStyle(.sidebar)
.task { await watchForConversationListChanges() }
.task { await model.triggerSync() }
}
private func watchForConversationListChanges() async {
for await event in xpcClient.eventStream() {
switch event {
case .conversationsUpdated:
model.setNeedsReload()
case .messagesUpdated(_):
await model.triggerSync()
case .updateStreamReconnected:
await model.triggerSync()
default:
break
}
}
}
// MARK: - Types
@Observable
class ViewModel
{
var conversations: [Display.Conversation]
var selectedConversation: Display.Conversation.ID?
private var needsReload: Bool = true
private let client = XPCClient()
public init(conversations: [Display.Conversation] = []) {
self.conversations = conversations
self.selectedConversation = nil
setNeedsReload()
}
func triggerSync() async {
do {
try await client.syncConversationList()
} catch {
print("Conversation List Sync Error: \(error)")
}
}
func setNeedsReload() {
needsReload = true
Task { @MainActor [weak self] in
guard let self else { return }
await reloadConversations()
}
}
func reloadConversations() async {
guard needsReload else { return }
needsReload = false
do {
let clientConversations = try await client.getConversations()
.map { Display.Conversation(from: $0) }
self.conversations = clientConversations
let unreadConversations = clientConversations.filter(\.isUnread)
await MainActor.run {
NSApplication.shared.dockTile.badgeLabel = unreadConversations.isEmpty ? nil : "\(unreadConversations.count)"
}
} catch {
print("Error reloading conversations: \(error)")
}
}
}
}
#Preview {
@Previewable @State var viewModel = ConversationListView.ViewModel(conversations: [
.init(id: "asdf", name: "Cool", participants: ["me"], messagePreview: "Hello there"),
.init(id: "gjkl", name: "Nice", participants: ["me"], messagePreview: "How are you"),
])
ConversationListView(model: $viewModel)
}