Files
nahbar/nahbar/AftermathRatingFlowView.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

147 lines
4.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
import SwiftData
// MARK: - AftermathRatingFlowView
// Scrollbarer Bewertungs-Flow für die Nachwirkungs-Bewertung (4 Fragen).
// Gleiche Interaktion wie MeetingRatingFlowView Fragen blenden nacheinander ein.
struct AftermathRatingFlowView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.nahbarTheme) var theme
@Environment(\.dismiss) private var dismiss
let moment: Moment
private let questions = RatingQuestion.aftermath // 4 Fragen
@State private var values: [Int?]
@State private var revealedCount: Int = 1
@State private var showSummary: Bool = false
init(moment: Moment) {
self.moment = moment
_values = State(initialValue: Array(repeating: nil, count: RatingQuestion.aftermath.count))
}
var body: some View {
NavigationStack {
Group {
if showSummary {
MeetingSummaryView(moment: moment, onDismiss: { dismiss() })
} else {
questionFlow
}
}
.navigationTitle("Nachwirkung")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Abbrechen") { dismiss() }
}
}
}
}
// MARK: - Scrollbarer Fragen-Flow
private var questionFlow: some View {
ScrollViewReader { proxy in
ScrollView {
VStack(spacing: 14) {
ForEach(0..<revealedCount, id: \.self) { i in
QuestionCard(
question: questions[i],
index: i,
total: questions.count,
isActive: i == revealedCount - 1,
value: $values[i],
onAnswer: { revealNext(after: i) },
onSkip: { revealNext(after: i) }
)
.id(i)
.transition(.asymmetric(
insertion: .opacity.combined(with: .move(edge: .bottom)),
removal: .identity
))
}
if revealedCount == questions.count {
saveButton
.id("save")
.transition(.asymmetric(
insertion: .opacity.combined(with: .move(edge: .bottom)),
removal: .identity
))
}
}
.padding(16)
.padding(.bottom, 32)
}
.background(theme.backgroundPrimary.ignoresSafeArea())
.onChange(of: revealedCount) { _, newCount in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
withAnimation(.easeOut(duration: 0.4)) {
proxy.scrollTo(newCount - 1, anchor: .top)
}
}
}
}
}
private var saveButton: some View {
Button { saveAftermath() } label: {
Text("Speichern")
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 15)
.background(theme.accent)
.clipShape(RoundedRectangle(cornerRadius: theme.radiusCard))
}
}
// MARK: - Navigation
private func revealNext(after index: Int) {
guard index == revealedCount - 1 else { return }
guard revealedCount < questions.count else { return }
withAnimation(.easeOut(duration: 0.35)) {
revealedCount += 1
}
}
// MARK: - Speichern
private func saveAftermath() {
for (i, q) in questions.enumerated() {
let rating = Rating(
category: q.category,
questionIndex: i,
value: values[i],
isAftermath: true,
moment: moment
)
modelContext.insert(rating)
}
moment.meetingStatus = .completed
moment.aftermathCompletedAt = Date()
AftermathNotificationManager.shared.cancelAftermath(momentID: moment.id)
do {
try modelContext.save()
AppEventLog.shared.record(
"Nachwirkung abgeschlossen für Moment \(moment.id.uuidString)",
level: .success, category: "Meeting"
)
} catch {
AppEventLog.shared.record(
"Fehler beim Speichern der Nachwirkung: \(error.localizedDescription)",
level: .error, category: "Meeting"
)
}
withAnimation { showSummary = true }
}
}