223 lines
7.5 KiB
Swift
223 lines
7.5 KiB
Swift
import SwiftUI
|
|
|
|
enum SybilAttachmentTone {
|
|
case composer
|
|
case user
|
|
case assistant
|
|
}
|
|
|
|
struct SybilAttachmentListView: View {
|
|
var attachments: [ChatAttachment]
|
|
var tone: SybilAttachmentTone
|
|
var onRemove: ((String) -> Void)? = nil
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
ForEach(attachments) { attachment in
|
|
Group {
|
|
if attachment.kind == .image {
|
|
imageCard(attachment)
|
|
} else {
|
|
textCard(attachment)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func imageCard(_ attachment: ChatAttachment) -> some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
if let image = SybilChatAttachmentSupport.image(for: attachment) {
|
|
Image(uiImage: image)
|
|
.resizable()
|
|
.scaledToFill()
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 180)
|
|
.clipped()
|
|
} else {
|
|
ZStack {
|
|
RoundedRectangle(cornerRadius: 14)
|
|
.fill(Color.black.opacity(0.18))
|
|
Image(systemName: "photo")
|
|
.font(.system(size: 22, weight: .medium))
|
|
.foregroundStyle(SybilTheme.textMuted)
|
|
}
|
|
.frame(height: 140)
|
|
}
|
|
|
|
HStack(alignment: .top, spacing: 10) {
|
|
Image(systemName: "photo")
|
|
.font(.system(size: 13, weight: .semibold))
|
|
.foregroundStyle(titleColor.opacity(0.92))
|
|
.frame(width: 26, height: 26)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.fill(Color.white.opacity(0.06))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.stroke(Color.white.opacity(0.08), lineWidth: 1)
|
|
)
|
|
)
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(attachment.filename)
|
|
.font(.sybil(.footnote, weight: .medium))
|
|
.foregroundStyle(titleColor)
|
|
.lineLimit(1)
|
|
|
|
Text(attachment.mimeType)
|
|
.font(.sybil(.caption2))
|
|
.foregroundStyle(SybilTheme.textMuted)
|
|
.lineLimit(1)
|
|
}
|
|
|
|
Spacer(minLength: 0)
|
|
|
|
if let onRemove {
|
|
removeButton(for: attachment.id, onRemove: onRemove)
|
|
}
|
|
}
|
|
.padding(12)
|
|
}
|
|
.background(cardBackground)
|
|
.clipShape(RoundedRectangle(cornerRadius: 14))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 14)
|
|
.stroke(cardBorder, lineWidth: 1)
|
|
)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func textCard(_ attachment: ChatAttachment) -> some View {
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
HStack(alignment: .top, spacing: 10) {
|
|
Image(systemName: "doc.text")
|
|
.font(.system(size: 13, weight: .semibold))
|
|
.foregroundStyle(titleColor.opacity(0.92))
|
|
.frame(width: 26, height: 26)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.fill(Color.white.opacity(0.06))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.stroke(Color.white.opacity(0.08), lineWidth: 1)
|
|
)
|
|
)
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(attachment.filename)
|
|
.font(.sybil(.footnote, weight: .medium))
|
|
.foregroundStyle(titleColor)
|
|
.lineLimit(1)
|
|
|
|
Text(
|
|
attachment.truncated == true
|
|
? "\(attachment.mimeType) • truncated"
|
|
: attachment.mimeType
|
|
)
|
|
.font(.sybil(.caption2))
|
|
.foregroundStyle(SybilTheme.textMuted)
|
|
.lineLimit(1)
|
|
}
|
|
|
|
Spacer(minLength: 0)
|
|
|
|
if let onRemove {
|
|
removeButton(for: attachment.id, onRemove: onRemove)
|
|
}
|
|
}
|
|
|
|
Text(SybilChatAttachmentSupport.previewText(for: attachment))
|
|
.font(.system(.caption, design: .monospaced))
|
|
.foregroundStyle(bodyColor)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(10)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 10)
|
|
.fill(Color.black.opacity(0.16))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 10)
|
|
.stroke(Color.white.opacity(0.05), lineWidth: 1)
|
|
)
|
|
)
|
|
}
|
|
.padding(12)
|
|
.background(cardBackground)
|
|
.clipShape(RoundedRectangle(cornerRadius: 14))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 14)
|
|
.stroke(cardBorder, lineWidth: 1)
|
|
)
|
|
}
|
|
|
|
private func removeButton(for attachmentID: String, onRemove: @escaping (String) -> Void) -> some View {
|
|
Button {
|
|
onRemove(attachmentID)
|
|
} label: {
|
|
Image(systemName: "xmark")
|
|
.font(.system(size: 11, weight: .bold))
|
|
.foregroundStyle(SybilTheme.textMuted)
|
|
.frame(width: 24, height: 24)
|
|
.background(
|
|
Circle()
|
|
.fill(Color.white.opacity(0.06))
|
|
)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityLabel("Remove attachment")
|
|
}
|
|
|
|
private var cardBackground: some ShapeStyle {
|
|
switch tone {
|
|
case .composer:
|
|
return AnyShapeStyle(
|
|
LinearGradient(
|
|
colors: [SybilTheme.surface.opacity(0.86), SybilTheme.surfaceStrong.opacity(0.78)],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
)
|
|
case .user:
|
|
return AnyShapeStyle(Color.black.opacity(0.14))
|
|
case .assistant:
|
|
return AnyShapeStyle(
|
|
LinearGradient(
|
|
colors: [SybilTheme.surface.opacity(0.58), SybilTheme.surfaceStrong.opacity(0.42)],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
private var cardBorder: Color {
|
|
switch tone {
|
|
case .composer:
|
|
return SybilTheme.border.opacity(0.82)
|
|
case .user:
|
|
return Color.white.opacity(0.12)
|
|
case .assistant:
|
|
return SybilTheme.border.opacity(0.58)
|
|
}
|
|
}
|
|
|
|
private var titleColor: Color {
|
|
switch tone {
|
|
case .composer, .assistant:
|
|
return SybilTheme.text
|
|
case .user:
|
|
return SybilTheme.text
|
|
}
|
|
}
|
|
|
|
private var bodyColor: Color {
|
|
switch tone {
|
|
case .composer, .assistant:
|
|
return SybilTheme.text.opacity(0.94)
|
|
case .user:
|
|
return SybilTheme.text.opacity(0.96)
|
|
}
|
|
}
|
|
}
|