~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atirc-ios-client

0
1
main
190 lines · 6.0 KB Raw History
//
//  AskView.swift
//  AT-IRC
//
//  Owner questions: availability toggle, pending/all list, and answering with
//  an option button or free text.
//

import SwiftUI

struct AskView: View {
    @Bindable var store: ChatStore
    @Environment(\.dismiss) private var dismiss
    @State private var showAll = false

    private var visibleAsks: [AskQuestion] {
        showAll ? store.asks : store.pendingAsks
    }

    var body: some View {
        NavigationStack {
            List {
                availabilitySection
                filterSection
                questionsSection
            }
            .navigationTitle("Questions")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Done") { dismiss() }
                }
            }
            .refreshable { await store.refreshAsks(force: true) }
            .task { await store.refreshAsks(force: true) }
        }
    }

    private var availabilitySection: some View {
        Section {
            Toggle(isOn: Binding(
                get: { store.ownerMode != "autonomous" },
                set: { available in
                    Task { await store.setOwnerMode(available ? "available" : "autonomous") }
                }
            )) {
                VStack(alignment: .leading, spacing: 2) {
                    Text(store.ownerMode == "autonomous" ? "Away" : "Available")
                    Text(store.ownerMode == "autonomous"
                         ? "Agents research and decide on their own; decisions and ADRs show up here."
                         : "New questions notify you and wait for your answer.")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
            }
            if let error = store.asksError {
                Text(error)
                    .font(.caption)
                    .foregroundStyle(.red)
            }
        }
    }

    private var filterSection: some View {
        Section {
            Picker("Show", selection: $showAll) {
                Text("Pending").tag(false)
                Text("All").tag(true)
            }
            .pickerStyle(.segmented)
        }
    }

    private var questionsSection: some View {
        Section("Questions") {
            if visibleAsks.isEmpty {
                Text(showAll ? "No questions yet." : "No pending questions.")
                    .foregroundStyle(.secondary)
            } else {
                ForEach(visibleAsks) { ask in
                    AskRow(ask: ask, store: store)
                }
            }
        }
    }
}

private struct AskRow: View {
    let ask: AskQuestion
    let store: ChatStore
    @State private var reply = ""
    @State private var sending = false

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Text(ask.asker)
                    .font(.caption.weight(.semibold))
                Spacer()
                Text(ask.status.capitalized)
                    .font(.caption2)
                    .foregroundStyle(statusColor)
            }
            Text(ask.question)
                .font(.body)
            if let context = ask.context, !context.isEmpty {
                Text(context)
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
            HStack(spacing: 8) {
                if let channel = ask.channel, !channel.isEmpty {
                    Text(channel)
                }
                if let date = ask.date {
                    Text(date, format: .relative(presentation: .named))
                }
            }
            .font(.caption2)
            .foregroundStyle(.tertiary)

            switch ask.status {
            case "pending":
                pendingControls
            case "answered":
                Text("Answered by \(ask.answeredBy ?? "owner"): \(ask.answer ?? "")")
                    .font(.callout)
            case "decided":
                Text("Decided by \(ask.decidedBy ?? "agent"): \(ask.decision ?? ask.answer ?? "")")
                    .font(.callout)
                if let adr = ask.adr, !adr.isEmpty {
                    if let url = URL(string: adr), url.scheme != nil {
                        Link("ADR: \(adr)", destination: url)
                            .font(.caption)
                    } else {
                        Text("ADR: \(adr)")
                            .font(.caption2)
                            .foregroundStyle(.secondary)
                    }
                }
            default:
                EmptyView()
            }
        }
        .padding(.vertical, 4)
    }

    @ViewBuilder
    private var pendingControls: some View {
        if let options = ask.options, !options.isEmpty {
            ForEach(Array(options.enumerated()), id: \.offset) { index, option in
                Button(option) { submit(option, choice: index) }
                    .buttonStyle(.bordered)
                    .disabled(sending)
            }
        }
        HStack {
            TextField("Answer", text: $reply)
                .textFieldStyle(.roundedBorder)
            Button {
                submit(reply, choice: nil)
            } label: {
                Image(systemName: "arrow.up.circle.fill")
                    .font(.title2)
            }
            .buttonStyle(.plain)
            .disabled(sending || reply.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
        }
    }

    private var statusColor: Color {
        switch ask.status {
        case "pending": return .orange
        case "answered": return .green
        case "decided": return .blue
        default: return .gray
        }
    }

    private func submit(_ answer: String, choice: Int?) {
        let text = answer.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !text.isEmpty || choice != nil else { return }
        sending = true
        Task {
            await store.answer(ask, answer: text, choice: choice)
            sending = false
            reply = ""
        }
    }
}