Adds getting/sending messages
This commit is contained in:
@@ -5,3 +5,127 @@
|
||||
// Created by James Magahern on 8/24/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ChatTranscriptView: View
|
||||
{
|
||||
@Binding var model: ViewModel
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 17.0) {
|
||||
ForEach($model.messages.reversed()) { message in
|
||||
MessageCellView(message: message)
|
||||
.id(message.id)
|
||||
.scaleEffect(CGSize(width: 1.0, height: -1.0))
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.scaleEffect(CGSize(width: 1.0, height: -1.0))
|
||||
}
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
@Observable
|
||||
class ViewModel
|
||||
{
|
||||
var messages: [Display.Message]
|
||||
var displayedConversation: Display.Conversation.ID? = nil
|
||||
|
||||
init(messages: [Display.Message] = []) {
|
||||
self.messages = messages
|
||||
observeDisplayedConversation()
|
||||
}
|
||||
|
||||
private func observeDisplayedConversation() {
|
||||
withObservationTracking {
|
||||
_ = displayedConversation
|
||||
} onChange: {
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else { return }
|
||||
|
||||
await loadMessages()
|
||||
observeDisplayedConversation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadMessages() async {
|
||||
self.messages = []
|
||||
|
||||
guard let displayedConversation else { return }
|
||||
|
||||
do {
|
||||
let client = XPCClient()
|
||||
let messages = try await client.getMessages(conversationId: displayedConversation)
|
||||
self.messages = messages.map { Display.Message(from: $0) }
|
||||
} catch {
|
||||
print("Message fetch error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageCellView: View
|
||||
{
|
||||
@Binding var message: Display.Message
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack(alignment: .bottom) {
|
||||
if message.isFromMe { Spacer(minLength: .minimumBubbleHorizontalPadding) }
|
||||
|
||||
TextCellContentView(text: message.text, isFromMe: message.isFromMe)
|
||||
.mask {
|
||||
UnevenRoundedRectangle(cornerRadii: RectangleCornerRadii(
|
||||
topLeading: message.isFromMe ? .dominantCornerRadius : .minorCornerRadius,
|
||||
bottomLeading: .dominantCornerRadius,
|
||||
bottomTrailing: .dominantCornerRadius,
|
||||
topTrailing: message.isFromMe ? .minorCornerRadius : .dominantCornerRadius,
|
||||
))
|
||||
}
|
||||
|
||||
if !message.isFromMe { Spacer(minLength: .minimumBubbleHorizontalPadding) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
private struct TextCellContentView: View
|
||||
{
|
||||
let text: String
|
||||
let isFromMe: Bool
|
||||
|
||||
var body: some View {
|
||||
let bubbleColor: Color = isFromMe ? .blue : Color(.systemGray)
|
||||
let textColor: Color = isFromMe ? .white : .primary
|
||||
|
||||
HStack {
|
||||
Text(text)
|
||||
.foregroundStyle(textColor)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.horizontal, 16.0)
|
||||
.padding(.vertical, 10.0)
|
||||
.background(bubbleColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension CGFloat {
|
||||
static let dominantCornerRadius = 16.0
|
||||
static let minorCornerRadius = 4.0
|
||||
static let minimumBubbleHorizontalPadding = 80.0
|
||||
}
|
||||
|
||||
#Preview {
|
||||
@Previewable @State var model = ChatTranscriptView.ViewModel(messages: [
|
||||
.init(sender: .me, text: "Hello, how are you?"),
|
||||
.init(sender: .counterpart("Bob"), text: "I am doing fine!")
|
||||
])
|
||||
|
||||
ChatTranscriptView(model: $model)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user