mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-08 19:19:42 +00:00
108 lines
4.7 KiB
Swift
108 lines
4.7 KiB
Swift
import HapiUI
|
|
import SwiftUI
|
|
|
|
/// Static answer cards, never the actionable permission form. Indices identify
|
|
/// rows so malformed duplicate question IDs cannot crash SwiftUI reconciliation.
|
|
struct QuestionDetailsView: View {
|
|
let questions: [QuestionDetail]
|
|
@Environment(\.hapiTheme) private var theme
|
|
@Environment(\.hapiTypography) private var typography
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 18) {
|
|
ForEach(Array(questions.enumerated()), id: \.offset) { index, question in
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
if let header = question.header {
|
|
Text(verbatim: header).font(typography.captionFont).foregroundStyle(theme.textSecondary)
|
|
}
|
|
if !question.question.isEmpty {
|
|
QuestionMarkdown(text: question.question)
|
|
.padding(10)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(theme.quoteBackground, in: RoundedRectangle(cornerRadius: 8))
|
|
}
|
|
ForEach(Array(question.options.enumerated()), id: \.offset) { optionIndex, option in
|
|
QuestionAnswerCard(
|
|
text: option.label, description: option.description, markdown: true,
|
|
selected: option.selected, multiple: question.multiple, showControl: question.hasAnswers
|
|
)
|
|
.accessibilityIdentifier("question-\(index)-option-\(optionIndex)")
|
|
}
|
|
ForEach(Array(question.otherAnswers.enumerated()), id: \.offset) { _, answer in
|
|
QuestionAnswerCard(
|
|
text: answer, caption: question.options.isEmpty ? nil : String(localized: "Custom answer"),
|
|
selected: true, multiple: question.multiple
|
|
)
|
|
}
|
|
if let note = question.note {
|
|
QuestionAnswerCard(
|
|
text: note, caption: question.options.isEmpty ? nil : String(localized: "Note"),
|
|
selected: true, multiple: false
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct QuestionAnswerCard: View {
|
|
let text: String
|
|
var description: String?
|
|
var caption: String?
|
|
var markdown = false
|
|
let selected: Bool
|
|
let multiple: Bool
|
|
var showControl = true
|
|
@Environment(\.hapiTheme) private var theme
|
|
@Environment(\.hapiTypography) private var typography
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: 8) {
|
|
if showControl {
|
|
Image(systemName: multiple
|
|
? (selected ? "checkmark.square.fill" : "square")
|
|
: (selected ? "largecircle.fill.circle" : "circle"))
|
|
.foregroundStyle(selected ? theme.success : theme.textHint)
|
|
.font(typography.bodyFont)
|
|
.accessibilityHidden(true)
|
|
}
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
if let caption {
|
|
Text(verbatim: caption).font(typography.captionFont).foregroundStyle(theme.textSecondary)
|
|
}
|
|
if markdown {
|
|
QuestionOptionLabel(label: text)
|
|
} else if text.count > toolTextPageSize {
|
|
ToolTextContent(language: nil, code: text)
|
|
} else {
|
|
Text(verbatim: text.isEmpty ? String(localized: "(empty)") : text)
|
|
.font(typography.bodyFont).textSelection(.enabled)
|
|
}
|
|
if let description, !description.isEmpty {
|
|
QuestionMarkdown(text: description).foregroundStyle(theme.textSecondary)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
.padding(10)
|
|
.background(selected ? theme.success.opacity(0.10) : theme.surface,
|
|
in: RoundedRectangle(cornerRadius: 10))
|
|
.overlay(RoundedRectangle(cornerRadius: 10).stroke(selected ? theme.success : theme.divider, lineWidth: 1))
|
|
.accessibilityElement(children: .combine)
|
|
.accessibilityValue(showControl ? (selected ? String(localized: "Selected") : String(localized: "Not selected")) : "")
|
|
.accessibilityAddTraits(selected ? .isSelected : [])
|
|
}
|
|
}
|
|
|
|
struct QuestionMarkdown: View {
|
|
let text: String
|
|
var body: some View {
|
|
if text.count > toolTextPageSize {
|
|
ToolTextContent(language: "markdown", code: text)
|
|
} else {
|
|
CachedMarkdownView(markdown: text)
|
|
}
|
|
}
|
|
}
|