Files
nahbar/nahbar/RatingQuestionView.swift
T
sven b477a3e04b Resolves #13 Fragebogen: scrollbarer Einblend-Flow
Statt Fragen einzeln zu ersetzen, werden sie jetzt nacheinander von
unten eingeblendet und bleiben sichtbar:
- Tippen auf einen Dot zeigt die nächste Frage darunter an
- "Überspringen" blendet ebenfalls die nächste Frage ein
- Beantwortete Fragen bleiben sichtbar und können angepasst werden
- Nach der letzten Frage erscheint ein "Speichern"-Button
- Gilt für Sofort-Bewertung (MeetingRatingFlowView) und
  Nachwirkung (AftermathRatingFlowView)
- Neuer QuestionCard-Component in RatingQuestionView.swift

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 05:47:58 +02:00

149 lines
5.1 KiB
Swift

import SwiftUI
// MARK: - QuestionCard
// Einzelne Bewertungsfrage als Card für den scrollbaren Flow.
// isActive = letzte sichtbare Frage (noch nicht bestätigt).
struct QuestionCard: View {
@Environment(\.nahbarTheme) var theme
let question: RatingQuestion
let index: Int
let total: Int
let isActive: Bool
@Binding var value: Int?
let onAnswer: () -> Void // Dot ausgewählt (nur wenn isActive)
let onSkip: () -> Void // Überspringen getippt (nur wenn isActive)
var body: some View {
VStack(alignment: .leading, spacing: 16) {
HStack {
Text("\(index + 1) / \(total)")
.font(.caption.weight(.medium))
.foregroundStyle(theme.contentTertiary)
Spacer()
HStack(spacing: 5) {
Image(systemName: question.category.icon)
.font(.caption.bold())
Text(LocalizedStringKey(question.category.rawValue))
.font(.caption.bold())
}
.foregroundStyle(question.category.color)
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(question.category.color.opacity(0.12), in: Capsule())
}
Text(LocalizedStringKey(question.text))
.font(.system(size: 16, weight: .semibold, design: theme.displayDesign))
.foregroundStyle(theme.contentPrimary)
.fixedSize(horizontal: false, vertical: true)
RatingDotPicker(
value: $value,
negativePole: question.negativePole,
positivePole: question.positivePole
)
.onChange(of: value) { _, newValue in
if isActive, newValue != nil {
onAnswer()
}
}
if isActive {
Button {
value = nil
onSkip()
} label: {
Text("Überspringen")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.buttonStyle(.plain)
}
}
.padding(16)
.background(theme.surfaceCard)
.clipShape(RoundedRectangle(cornerRadius: theme.radiusCard))
.opacity(isActive ? 1.0 : 0.75)
}
}
// MARK: - RatingQuestionView
// Zeigt eine einzelne Bewertungsfrage mit Kategorie-Badge, Fragetext,
// RatingDotPicker und "Überspringen"-Button.
struct RatingQuestionView: View {
let question: RatingQuestion
let index: Int // 0-basiert innerhalb des aktuellen Flows
let total: Int
@Binding var value: Int?
var body: some View {
VStack(spacing: 0) {
// Fortschrittsbalken
GeometryReader { geo in
ZStack(alignment: .leading) {
Rectangle()
.fill(Color(.systemGray5))
Rectangle()
.fill(question.category.color)
.frame(width: geo.size.width * CGFloat(index + 1) / CGFloat(total))
.animation(.easeInOut(duration: 0.3), value: index)
}
}
.frame(height: 4)
ScrollView {
VStack(spacing: 32) {
// Kategorie-Badge
HStack(spacing: 6) {
Image(systemName: question.category.icon)
.font(.caption.bold())
Text(LocalizedStringKey(question.category.rawValue))
.font(.caption.bold())
}
.foregroundStyle(question.category.color)
.padding(.horizontal, 14)
.padding(.vertical, 6)
.background(question.category.color.opacity(0.12), in: Capsule())
.padding(.top, 32)
// Fragetext
Text(LocalizedStringKey(question.text))
.font(.title3.weight(.semibold))
.multilineTextAlignment(.center)
.padding(.horizontal, 24)
// Picker
RatingDotPicker(value: $value,
negativePole: question.negativePole,
positivePole: question.positivePole)
.padding(.horizontal, 24)
// Überspringen
Button {
value = nil
} label: {
Text("Überspringen")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.buttonStyle(.plain)
.padding(.top, 8)
}
.padding(.bottom, 32)
}
}
}
}
#Preview {
@Previewable @State var val: Int? = nil
RatingQuestionView(
question: RatingQuestion.all[0],
index: 0,
total: 9,
value: $val
)
}