import AppKit import SwiftUI let otherStop = "\u{0}other" @MainActor func otherOpen(_ store: Store, _ key: String) -> Bool { (store.drafts?.hasTextAnswer(key) ?? false) || store.images.has(key) } struct AttentionCard: View { let open: Bool @ViewBuilder let content: Content var body: some View { content .padding(.horizontal, 12) .padding(.vertical, 8) .frame(maxWidth: .infinity, alignment: .leading) .background(open ? Palette.warnTint : Palette.warnFaint, in: RoundedRectangle(cornerRadius: 10)) .overlay(RoundedRectangle(cornerRadius: 10).stroke(open ? Palette.warn.opacity(0.55) : Palette.border)) } } struct AttentionHeader: View { let label: String let when: Date let open: Bool var body: some View { HStack(spacing: 8) { Text(label) .font(Fonts.font(11, weight: open ? 700 : 400)) .foregroundStyle(open ? Palette.warn : Palette.muted) WhenText(when: when) .font(Fonts.font(11)) .foregroundStyle(Palette.muted) } } } struct AttentionReply: View { let key: String let placeholder: String let busy: Bool var focus: FocusState.Binding let send: () -> Void @ViewBuilder let leading: Leading @ViewBuilder let trailing: Trailing var body: some View { VStack(alignment: .leading, spacing: 6) { OtherEntry(key: key, placeholder: placeholder, busy: busy, focus: focus, send: send) HStack(spacing: 8) { leading Spacer(minLength: 0) trailing } .disabled(busy) } } } private struct OtherEntry: View { let key: String let placeholder: String let busy: Bool var focus: FocusState.Binding let send: () -> Void @Environment(Store.self) private var store @FocusState private var editing: Bool @ViewState private var hover = false @ViewState private var text = "" var body: some View { let focused = focus.wrappedValue == otherStop Group { if otherOpen(store, key) { VStack(alignment: .leading, spacing: 6) { Thumbnails(key: key) TextEditor(text: $text) .font(Fonts.font(13)) .scrollContentBackground(.hidden) .writingToolsBehavior(.disabled) .focused($editing) .disabled(busy) .onKeyPress(.return, phases: .down) { press in let marked = (NSApp.keyWindow?.firstResponder as? NSTextView)?.hasMarkedText() ?? false if press.modifiers.contains(.shift) || marked { return .ignored } send() return .handled } .padding(.horizontal, 5) .padding(.vertical, 6) .frame(height: 66) .background(Palette.raised, in: RoundedRectangle(cornerRadius: 6)) .overlay(alignment: .topLeading) { if text.isEmpty { Text(placeholder) .foregroundStyle(Palette.muted) .padding(.horizontal, 10) .padding(.vertical, 6) .allowsHitTesting(false) } } .overlay(RoundedRectangle(cornerRadius: 6).stroke(editing ? Palette.accent : Palette.border)) .imageInput(key) } .onChange(of: editing) { if !editing { if trimmed(text).isEmpty && !store.images.has(key) && !busy { store.drafts?.dropTextAnswer(key) } store.drafts?.flush() } } } else { Text("Other…") .foregroundStyle(Palette.muted) .padding(.horizontal, 10) .padding(.vertical, 4) .frame(maxWidth: .infinity, alignment: .leading) .background(Palette.raised, in: RoundedRectangle(cornerRadius: 6)) .overlay(RoundedRectangle(cornerRadius: 6).stroke(hover || focused ? Palette.accent : Palette.border)) .overlay { if focused { RoundedRectangle(cornerRadius: 6).stroke(Palette.accent, lineWidth: 2).padding(-2) } } .opacity(busy ? 0.6 : 1) .contentShape(Rectangle()) .focusable(!busy) .focusEffectDisabled() .focused(focus, equals: otherStop) .onTapGesture { open() } .onKeyPress(keys: [.return, .space]) { _ in open() return .handled } .onHover { hover = $0 } } } .onAppear { text = store.drafts?.textAnswer(key) ?? "" } .onChange(of: text) { if text != store.drafts?.textAnswer(key) ?? "" { store.drafts?.setTextAnswer(key, text) } store.paneFocus.setDraft(key, !trimmed(text).isEmpty) } .onChange(of: store.drafts?.textAnswer(key)) { _, new in if (new ?? "") != text { text = new ?? "" } } .onDisappear { store.paneFocus.setDraft(key, false) } } private func open() { if busy { return } store.drafts?.setTextAnswer(key, "") DispatchQueue.main.async { editing = true } } }