From 402b5a5f8007152e723499818a141be2b645df52 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Mon, 25 Aug 2025 00:37:48 -0700 Subject: [PATCH] autosyncing, appearance tweaks --- kordophone2/ConversationListView.swift | 35 ++++++++++++++----- kordophone2/MessageEntryView.swift | 6 ++++ kordophone2/Models.swift | 5 ++- .../TranscriptDisplayItemViews.swift | 4 ++- .../Transcript/TranscriptDisplayItems.swift | 4 ++- kordophone2/Transcript/TranscriptView.swift | 11 ++++++ kordophone2/XPC/XPCClient.swift | 10 ++++++ 7 files changed, 64 insertions(+), 11 deletions(-) diff --git a/kordophone2/ConversationListView.swift b/kordophone2/ConversationListView.swift index 6feeb6e..37df92f 100644 --- a/kordophone2/ConversationListView.swift +++ b/kordophone2/ConversationListView.swift @@ -14,17 +14,27 @@ struct ConversationListView: View var body: some View { List($model.conversations, selection: $model.selectedConversations) { conv in - VStack(alignment: .leading) { - Text(conv.wrappedValue.displayName) - .bold() + let isUnread = conv.wrappedValue.unreadCount > 0 + + HStack(spacing: 0.0) { + Image(systemName: isUnread ? "circlebadge.fill" : "") + .foregroundStyle(.tint) + .frame(width: 10.0) - Text(conv.wrappedValue.messagePreview) + VStack(alignment: .leading) { + Text(conv.wrappedValue.displayName) + .bold() + + Text(conv.wrappedValue.messagePreview) + .foregroundStyle(.secondary) + } + .padding(8.0) } .id(conv.id) - .padding(8.0) } .listStyle(.sidebar) .task { await watchForConversationListChanges() } + .task { await model.triggerSync() } } private func watchForConversationListChanges() async { @@ -33,9 +43,9 @@ struct ConversationListView: View case .conversationsUpdated: model.setNeedsReload() case .messagesUpdated(_): - model.setNeedsReload() + await model.triggerSync() case .updateStreamReconnected: - model.setNeedsReload() + await model.triggerSync() default: break } @@ -51,6 +61,7 @@ struct ConversationListView: View var selectedConversations: Set private var needsReload: Bool = true + private let client = XPCClient() public init(conversations: [Display.Conversation] = []) { self.conversations = conversations @@ -58,6 +69,14 @@ struct ConversationListView: View 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 @@ -71,7 +90,7 @@ struct ConversationListView: View needsReload = false do { - let client = XPCClient() + let clientConversations = try await client.getConversations() .map { Display.Conversation(from: $0) } diff --git a/kordophone2/MessageEntryView.swift b/kordophone2/MessageEntryView.swift index 0b04aa8..30192f5 100644 --- a/kordophone2/MessageEntryView.swift +++ b/kordophone2/MessageEntryView.swift @@ -28,8 +28,10 @@ struct MessageEntryView: View .font(.body) .scrollDisabled(true) .padding(8.0) + .disabled(selectedConversation == nil) .background { RoundedRectangle(cornerRadius: 6.0) + .stroke(SeparatorShapeStyle()) .fill(.background) } @@ -41,6 +43,10 @@ struct MessageEntryView: View } .padding(10.0) } + + .onChange(of: selectedConversation) { oldValue, newValue in + viewModel.draftText = "" + } } // MARK: - Types diff --git a/kordophone2/Models.swift b/kordophone2/Models.swift index 2b01daa..223a80e 100644 --- a/kordophone2/Models.swift +++ b/kordophone2/Models.swift @@ -10,12 +10,13 @@ import XPC enum Display { - struct Conversation: Identifiable + struct Conversation: Identifiable, Hashable { let id: String let name: String? let participants: [String] let messagePreview: String + let unreadCount: Int var displayName: String { if let name, name.count > 0 { return name } @@ -27,6 +28,7 @@ enum Display self.name = c.displayName self.participants = c.participants self.messagePreview = c.lastMessagePreview ?? "" + self.unreadCount = c.unreadCount } init(id: String = UUID().uuidString, name: String? = nil, participants: [String], messagePreview: String) { @@ -34,6 +36,7 @@ enum Display self.name = name self.participants = participants self.messagePreview = messagePreview + self.unreadCount = 0 } } diff --git a/kordophone2/Transcript/TranscriptDisplayItemViews.swift b/kordophone2/Transcript/TranscriptDisplayItemViews.swift index 896834a..f26d191 100644 --- a/kordophone2/Transcript/TranscriptDisplayItemViews.swift +++ b/kordophone2/Transcript/TranscriptDisplayItemViews.swift @@ -44,7 +44,9 @@ struct TextBubbleItemView: View let isFromMe: Bool var body: some View { - let bubbleColor: Color = isFromMe ? .blue : Color(.systemGray) + let bubbleColor: Color = isFromMe ? .blue : Color(NSColor(name: "grayish", dynamicProvider: { appearance in + appearance.name == .darkAqua ? .darkGray : NSColor(white: 0.78, alpha: 1.0) + })) let textColor: Color = isFromMe ? .white : .primary BubbleView(isFromMe: isFromMe) { diff --git a/kordophone2/Transcript/TranscriptDisplayItems.swift b/kordophone2/Transcript/TranscriptDisplayItems.swift index 4d038d7..ea72a8f 100644 --- a/kordophone2/Transcript/TranscriptDisplayItems.swift +++ b/kordophone2/Transcript/TranscriptDisplayItems.swift @@ -32,7 +32,9 @@ extension TranscriptView.ViewModel } } - displayItems.append(.message(message)) + if !message.text.isEmpty { + displayItems.append(.message(message)) + } } let animation: Animation? = animated ? .default : nil diff --git a/kordophone2/Transcript/TranscriptView.swift b/kordophone2/Transcript/TranscriptView.swift index a9a20e3..129e1ba 100644 --- a/kordophone2/Transcript/TranscriptView.swift +++ b/kordophone2/Transcript/TranscriptView.swift @@ -113,6 +113,7 @@ struct TranscriptView: View Task { @MainActor [weak self] in guard let self else { return } + await markAsRead() await triggerSync() setNeedsReload(animated: false) @@ -121,6 +122,16 @@ struct TranscriptView: View } } + func markAsRead() async { + guard let displayedConversation else { return } + + do { + try await client.markConversationAsRead(conversationId: displayedConversation) + } catch { + print("Error triggering sync: \(error)") + } + } + func triggerSync() async { guard let displayedConversation else { return } diff --git a/kordophone2/XPC/XPCClient.swift b/kordophone2/XPC/XPCClient.swift index fb78d8a..878756f 100644 --- a/kordophone2/XPC/XPCClient.swift +++ b/kordophone2/XPC/XPCClient.swift @@ -89,6 +89,16 @@ final class XPCClient _ = try await sendSync(req) } + public func syncConversationList() async throws { + let req = makeRequest(method: "SyncConversationList") + _ = try await sendSync(req) + } + + public func markConversationAsRead(conversationId: String) async throws { + let req = makeRequest(method: "MarkConversationAsRead", arguments: ["conversation_id": xpcString(conversationId)]) + _ = try await sendSync(req) + } + public func getMessages(conversationId: String, limit: Int = 100, offset: Int = 0) async throws -> [Serialized.Message] { var args: [String: xpc_object_t] = [:] args["conversation_id"] = xpcString(conversationId)