autosyncing, appearance tweaks
This commit is contained in:
@@ -14,17 +14,27 @@ struct ConversationListView: View
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List($model.conversations, selection: $model.selectedConversations) { conv in
|
List($model.conversations, selection: $model.selectedConversations) { conv in
|
||||||
VStack(alignment: .leading) {
|
let isUnread = conv.wrappedValue.unreadCount > 0
|
||||||
Text(conv.wrappedValue.displayName)
|
|
||||||
.bold()
|
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)
|
.id(conv.id)
|
||||||
.padding(8.0)
|
|
||||||
}
|
}
|
||||||
.listStyle(.sidebar)
|
.listStyle(.sidebar)
|
||||||
.task { await watchForConversationListChanges() }
|
.task { await watchForConversationListChanges() }
|
||||||
|
.task { await model.triggerSync() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func watchForConversationListChanges() async {
|
private func watchForConversationListChanges() async {
|
||||||
@@ -33,9 +43,9 @@ struct ConversationListView: View
|
|||||||
case .conversationsUpdated:
|
case .conversationsUpdated:
|
||||||
model.setNeedsReload()
|
model.setNeedsReload()
|
||||||
case .messagesUpdated(_):
|
case .messagesUpdated(_):
|
||||||
model.setNeedsReload()
|
await model.triggerSync()
|
||||||
case .updateStreamReconnected:
|
case .updateStreamReconnected:
|
||||||
model.setNeedsReload()
|
await model.triggerSync()
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -51,6 +61,7 @@ struct ConversationListView: View
|
|||||||
var selectedConversations: Set<Display.Conversation.ID>
|
var selectedConversations: Set<Display.Conversation.ID>
|
||||||
|
|
||||||
private var needsReload: Bool = true
|
private var needsReload: Bool = true
|
||||||
|
private let client = XPCClient()
|
||||||
|
|
||||||
public init(conversations: [Display.Conversation] = []) {
|
public init(conversations: [Display.Conversation] = []) {
|
||||||
self.conversations = conversations
|
self.conversations = conversations
|
||||||
@@ -58,6 +69,14 @@ struct ConversationListView: View
|
|||||||
setNeedsReload()
|
setNeedsReload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func triggerSync() async {
|
||||||
|
do {
|
||||||
|
try await client.syncConversationList()
|
||||||
|
} catch {
|
||||||
|
print("Conversation List Sync Error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setNeedsReload() {
|
func setNeedsReload() {
|
||||||
needsReload = true
|
needsReload = true
|
||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
@@ -71,7 +90,7 @@ struct ConversationListView: View
|
|||||||
needsReload = false
|
needsReload = false
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let client = XPCClient()
|
|
||||||
let clientConversations = try await client.getConversations()
|
let clientConversations = try await client.getConversations()
|
||||||
.map { Display.Conversation(from: $0) }
|
.map { Display.Conversation(from: $0) }
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ struct MessageEntryView: View
|
|||||||
.font(.body)
|
.font(.body)
|
||||||
.scrollDisabled(true)
|
.scrollDisabled(true)
|
||||||
.padding(8.0)
|
.padding(8.0)
|
||||||
|
.disabled(selectedConversation == nil)
|
||||||
.background {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 6.0)
|
RoundedRectangle(cornerRadius: 6.0)
|
||||||
|
.stroke(SeparatorShapeStyle())
|
||||||
.fill(.background)
|
.fill(.background)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +43,10 @@ struct MessageEntryView: View
|
|||||||
}
|
}
|
||||||
.padding(10.0)
|
.padding(10.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onChange(of: selectedConversation) { oldValue, newValue in
|
||||||
|
viewModel.draftText = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Types
|
// MARK: - Types
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import XPC
|
|||||||
|
|
||||||
enum Display
|
enum Display
|
||||||
{
|
{
|
||||||
struct Conversation: Identifiable
|
struct Conversation: Identifiable, Hashable
|
||||||
{
|
{
|
||||||
let id: String
|
let id: String
|
||||||
let name: String?
|
let name: String?
|
||||||
let participants: [String]
|
let participants: [String]
|
||||||
let messagePreview: String
|
let messagePreview: String
|
||||||
|
let unreadCount: Int
|
||||||
|
|
||||||
var displayName: String {
|
var displayName: String {
|
||||||
if let name, name.count > 0 { return name }
|
if let name, name.count > 0 { return name }
|
||||||
@@ -27,6 +28,7 @@ enum Display
|
|||||||
self.name = c.displayName
|
self.name = c.displayName
|
||||||
self.participants = c.participants
|
self.participants = c.participants
|
||||||
self.messagePreview = c.lastMessagePreview ?? ""
|
self.messagePreview = c.lastMessagePreview ?? ""
|
||||||
|
self.unreadCount = c.unreadCount
|
||||||
}
|
}
|
||||||
|
|
||||||
init(id: String = UUID().uuidString, name: String? = nil, participants: [String], messagePreview: String) {
|
init(id: String = UUID().uuidString, name: String? = nil, participants: [String], messagePreview: String) {
|
||||||
@@ -34,6 +36,7 @@ enum Display
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.participants = participants
|
self.participants = participants
|
||||||
self.messagePreview = messagePreview
|
self.messagePreview = messagePreview
|
||||||
|
self.unreadCount = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ struct TextBubbleItemView: View
|
|||||||
let isFromMe: Bool
|
let isFromMe: Bool
|
||||||
|
|
||||||
var body: some View {
|
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
|
let textColor: Color = isFromMe ? .white : .primary
|
||||||
|
|
||||||
BubbleView(isFromMe: isFromMe) {
|
BubbleView(isFromMe: isFromMe) {
|
||||||
|
|||||||
@@ -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
|
let animation: Animation? = animated ? .default : nil
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ struct TranscriptView: View
|
|||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
|
await markAsRead()
|
||||||
await triggerSync()
|
await triggerSync()
|
||||||
|
|
||||||
setNeedsReload(animated: false)
|
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 {
|
func triggerSync() async {
|
||||||
guard let displayedConversation else { return }
|
guard let displayedConversation else { return }
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,16 @@ final class XPCClient
|
|||||||
_ = try await sendSync(req)
|
_ = 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] {
|
public func getMessages(conversationId: String, limit: Int = 100, offset: Int = 0) async throws -> [Serialized.Message] {
|
||||||
var args: [String: xpc_object_t] = [:]
|
var args: [String: xpc_object_t] = [:]
|
||||||
args["conversation_id"] = xpcString(conversationId)
|
args["conversation_id"] = xpcString(conversationId)
|
||||||
|
|||||||
Reference in New Issue
Block a user