Private
Public Access
1
0
Files
Kordophone/osx/kordophone2/ConversationListView.swift

124 lines
3.8 KiB
Swift
Raw Normal View History

2025-08-24 16:24:21 -07:00
//
// ConversationListView.swift
// kordophone2
//
// Created by James Magahern on 8/24/25.
//
import SwiftUI
struct ConversationListView: View
{
@Binding var model: ViewModel
2025-08-24 18:41:42 -07:00
@Environment(\.xpcClient) private var xpcClient
2025-08-24 16:24:21 -07:00
var body: some View {
List($model.conversations, selection: $model.selectedConversations) { conv in
2025-08-25 00:37:48 -07:00
let isUnread = conv.wrappedValue.unreadCount > 0
HStack(spacing: 0.0) {
2025-08-29 18:49:00 -06:00
if isUnread {
Image(systemName: "circlebadge.fill")
.foregroundStyle(.tint)
.frame(width: 10.0)
} else {
Rectangle()
.foregroundStyle(.clear)
.frame(width: 10.0)
}
2025-08-24 16:24:21 -07:00
2025-08-25 00:37:48 -07:00
VStack(alignment: .leading) {
Text(conv.wrappedValue.displayName)
.bold()
Text(conv.wrappedValue.messagePreview)
.foregroundStyle(.secondary)
}
.padding(8.0)
2025-08-24 16:24:21 -07:00
}
2025-08-24 18:41:42 -07:00
.id(conv.id)
2025-08-24 16:24:21 -07:00
}
.listStyle(.sidebar)
2025-08-24 18:41:42 -07:00
.task { await watchForConversationListChanges() }
2025-08-25 00:37:48 -07:00
.task { await model.triggerSync() }
2025-08-24 18:41:42 -07:00
}
private func watchForConversationListChanges() async {
for await event in xpcClient.eventStream() {
switch event {
case .conversationsUpdated:
model.setNeedsReload()
case .messagesUpdated(_):
2025-08-25 00:37:48 -07:00
await model.triggerSync()
2025-08-24 18:41:42 -07:00
case .updateStreamReconnected:
2025-08-25 00:37:48 -07:00
await model.triggerSync()
2025-08-24 18:41:42 -07:00
default:
break
}
}
2025-08-24 16:24:21 -07:00
}
// MARK: - Types
@Observable
class ViewModel
{
var conversations: [Display.Conversation]
var selectedConversations: Set<Display.Conversation.ID>
2025-08-24 18:41:42 -07:00
private var needsReload: Bool = true
2025-08-25 00:37:48 -07:00
private let client = XPCClient()
2025-08-24 18:41:42 -07:00
2025-08-24 16:24:21 -07:00
public init(conversations: [Display.Conversation] = []) {
self.conversations = conversations
self.selectedConversations = Set()
2025-08-24 18:41:42 -07:00
setNeedsReload()
}
2025-08-25 00:37:48 -07:00
func triggerSync() async {
do {
try await client.syncConversationList()
} catch {
print("Conversation List Sync Error: \(error)")
}
}
2025-08-24 18:41:42 -07:00
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 {
2025-08-25 00:37:48 -07:00
2025-08-24 18:41:42 -07:00
let clientConversations = try await client.getConversations()
.map { Display.Conversation(from: $0) }
self.conversations = clientConversations
2025-09-09 18:54:14 -07:00
let unreadConversations = clientConversations.filter(\.isUnread)
await MainActor.run {
NSApplication.shared.dockTile.badgeLabel = unreadConversations.isEmpty ? nil : "\(unreadConversations.count)"
}
2025-08-24 18:41:42 -07:00
} catch {
print("Error reloading conversations: \(error)")
}
2025-08-24 16:24:21 -07:00
}
}
}
#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)
}