Private
Public Access
1
0
Files
Kordophone/kordophone2/Transcript/TranscriptDisplayItemViews.swift

151 lines
4.0 KiB
Swift

//
// TranscriptDisplayItemViews.swift
// kordophone2
//
// Created by James Magahern on 8/24/25.
//
import SwiftUI
struct BubbleView<Content: View>: View
{
let isFromMe: Bool
let content: () -> Content
init(isFromMe: Bool, @ViewBuilder content: @escaping () -> Content) {
self.isFromMe = isFromMe
self.content = content
}
var body: some View {
VStack {
HStack(alignment: .bottom) {
if isFromMe { Spacer(minLength: .minimumBubbleHorizontalPadding) }
content()
.mask {
UnevenRoundedRectangle(cornerRadii: RectangleCornerRadii(
topLeading: isFromMe ? .dominantCornerRadius : .minorCornerRadius,
bottomLeading: .dominantCornerRadius,
bottomTrailing: .dominantCornerRadius,
topTrailing: isFromMe ? .minorCornerRadius : .dominantCornerRadius,
))
}
if !isFromMe { Spacer(minLength: .minimumBubbleHorizontalPadding) }
}
}
}
}
struct TextBubbleItemView: View
{
let text: String
let isFromMe: Bool
var body: some View {
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) {
HStack {
Text(text)
.foregroundStyle(textColor)
.multilineTextAlignment(.leading)
}
.fixedSize(horizontal: false, vertical: true)
.padding(.horizontal, 16.0)
.padding(.vertical, 10.0)
.background(bubbleColor)
}
}
}
struct ImageItemView: View
{
let isFromMe: Bool
let attachment: Display.ImageAttachment
@State private var img: NSImage?
@Environment(\.xpcClient) var xpcClient
var body: some View {
BubbleView(isFromMe: isFromMe) {
if let img {
Image(nsImage: img)
.resizable()
.scaledToFit()
} else {
ProgressView()
}
}
.task {
do {
let handle = try await xpcClient.openAttachmentFileHandle(
attachmentId: attachment.id,
preview: true
)
try? handle.seek(toOffset: 0)
if let data = try? handle.readToEnd(),
let ns = NSImage(data: data) {
img = ns
}
} catch {
print("Attachment file handle acquisition error: \(error)")
}
}
}
}
struct PlaceholderImageItemView: View
{
let isFromMe: Bool
let size: CGSize
init(isFromMe: Bool, size: CGSize?) {
self.isFromMe = isFromMe
self.size = size ?? CGSize(width: 250.0, height: 100.0)
}
var body: some View {
BubbleView(isFromMe: isFromMe) {
Color.gray
.frame(width: size.width, height: size.height)
}
}
}
struct DateItemView: View
{
let date: Date
private let formatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "EEEE HH:mm"
return f
}()
var body: some View {
VStack {
Spacer(minLength: 34.0)
Text(formatter.string(from: date))
.font(.caption)
.foregroundStyle(.secondary)
.padding(.vertical, 4)
Spacer()
}
}
}
fileprivate extension CGFloat {
static let dominantCornerRadius = 16.0
static let minorCornerRadius = 4.0
static let minimumBubbleHorizontalPadding = 80.0
}