Add guided reflection flow with mood-adaptive CBT/ACT questions
Walks users through 3-4 guided questions based on mood category: positive (great/good) gets gratitude-oriented questions, neutral (average) gets exploratory questions, and negative (bad/horrible) gets empathetic questions. Stored as JSON in MoodEntryModel, integrated into PDF reports, AI summaries, and CSV export. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,7 @@ struct EntryDetailView: View {
|
||||
@State private var showDeleteConfirmation = false
|
||||
@State private var showFullScreenPhoto = false
|
||||
@State private var selectedPhotoItem: PhotosPickerItem?
|
||||
@State private var showReflectionFlow = false
|
||||
@State private var selectedMood: Mood?
|
||||
|
||||
private var currentMood: Mood {
|
||||
@@ -184,6 +185,11 @@ struct EntryDetailView: View {
|
||||
// Mood section
|
||||
moodSection
|
||||
|
||||
// Guided reflection section
|
||||
if currentMood != .missing && currentMood != .placeholder {
|
||||
reflectionSection
|
||||
}
|
||||
|
||||
// Notes section
|
||||
notesSection
|
||||
|
||||
@@ -218,6 +224,9 @@ struct EntryDetailView: View {
|
||||
.sheet(isPresented: $showNoteEditor) {
|
||||
NoteEditorView(entry: entry)
|
||||
}
|
||||
.sheet(isPresented: $showReflectionFlow) {
|
||||
GuidedReflectionView(entry: entry)
|
||||
}
|
||||
.alert("Delete Entry", isPresented: $showDeleteConfirmation) {
|
||||
Button("Delete", role: .destructive) {
|
||||
onDelete()
|
||||
@@ -417,6 +426,74 @@ struct EntryDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var reflectionSection: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack {
|
||||
Text(String(localized: "guided_reflection_title"))
|
||||
.font(.headline)
|
||||
.foregroundColor(textColor)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
AnalyticsManager.shared.track(.reflectionStarted)
|
||||
showReflectionFlow = true
|
||||
} label: {
|
||||
Text(entry.reflectionJSON != nil
|
||||
? String(localized: "guided_reflection_edit")
|
||||
: String(localized: "guided_reflection_begin"))
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
AnalyticsManager.shared.track(.reflectionStarted)
|
||||
showReflectionFlow = true
|
||||
} label: {
|
||||
HStack {
|
||||
if let json = entry.reflectionJSON,
|
||||
let reflection = GuidedReflection.decode(from: json),
|
||||
reflection.answeredCount > 0 {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(reflection.responses.first(where: {
|
||||
!$0.answer.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
})?.answer ?? "")
|
||||
.font(.body)
|
||||
.foregroundColor(textColor)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(3)
|
||||
|
||||
Text(String(localized: "guided_reflection_answered_count \(reflection.answeredCount) \(reflection.totalQuestions)"))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} else {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "sparkles")
|
||||
.foregroundStyle(.secondary)
|
||||
Text(String(localized: "guided_reflection_empty_prompt"))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color(.systemBackground))
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
private func weatherSection(_ weatherData: WeatherData) -> some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Weather")
|
||||
|
||||
Reference in New Issue
Block a user