Add CBT-based guided reflection questions with clinical info sheet

Replace generic journaling prompts with evidence-based therapeutic
techniques: CBT thought record for negative moods, ACT cognitive
defusion for neutral, and behavioral activation for positive. Each
question now shows a clinical step label (e.g. SITUATION, REFRAME).
Added info button linking to a new sheet explaining the techniques
with citations to Beck, Harris, and Martell/Dimidjian.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-17 22:39:30 -05:00
parent 916be6a1e0
commit 99314b8e6a
4 changed files with 15582 additions and 15260 deletions

View File

@@ -10,9 +10,9 @@ import Foundation
// MARK: - Mood Category
enum MoodCategory: String, Codable {
case positive // great, good 3 questions
case neutral // average 4 questions
case negative // bad, horrible 4 questions
case positive // great, good 3 questions (Behavioral Activation)
case neutral // average 4 questions (ACT Cognitive Defusion)
case negative // bad, horrible 4 questions (CBT Thought Record)
init(from mood: Mood) {
switch mood {
@@ -29,6 +29,41 @@ enum MoodCategory: String, Codable {
case .neutral, .negative: return 4
}
}
/// The therapeutic technique name for display purposes.
var techniqueName: String {
switch self {
case .positive: return "Behavioral Activation"
case .neutral: return "Acceptance & Commitment Therapy"
case .negative: return "Cognitive Behavioral Therapy"
}
}
/// Short CBT step labels shown above each question.
var stepLabels: [String] {
switch self {
case .negative:
return [
String(localized: "Situation"),
String(localized: "Automatic Thought"),
String(localized: "Perspective Check"),
String(localized: "Reframe"),
]
case .neutral:
return [
String(localized: "Awareness"),
String(localized: "Thought"),
String(localized: "Defusion"),
String(localized: "Values"),
]
case .positive:
return [
String(localized: "Activity"),
String(localized: "Awareness"),
String(localized: "Planning"),
]
}
}
}
// MARK: - Guided Reflection

View File

@@ -0,0 +1,124 @@
//
// GuidedReflectionInfoView.swift
// Reflect
//
// Explains the clinical techniques behind the guided reflection questions.
//
import SwiftUI
struct GuidedReflectionInfoView: View {
@Environment(\.dismiss) private var dismiss
private let cbtURL = URL(string: "https://www.ncbi.nlm.nih.gov/books/NBK470241/")!
private let actURL = URL(string: "https://link.springer.com/article/10.1007/s40732-017-0254-z")!
private let baURL = URL(string: "https://www.psychologytools.com/self-help/behavioral-activation")!
var body: some View {
NavigationStack {
ScrollView {
VStack(alignment: .leading, spacing: 28) {
// Intro
Text(String(localized: "guided_reflection_about_body"))
.font(.body)
.foregroundStyle(.secondary)
// CBT
techniqueSection(
icon: "brain.head.profile",
color: .red,
title: String(localized: "guided_reflection_about_cbt_title"),
body: String(localized: "guided_reflection_about_cbt_body"),
citation: "Beck, J. S. (2020). Cognitive Behavior Therapy: Basics and Beyond, 3rd ed.",
url: cbtURL
)
// ACT
techniqueSection(
icon: "eye",
color: .orange,
title: String(localized: "guided_reflection_about_act_title"),
body: String(localized: "guided_reflection_about_act_body"),
citation: "Harris, R. (2009). ACT Made Simple. New Harbinger Publications.",
url: actURL
)
// BA
techniqueSection(
icon: "figure.walk",
color: .green,
title: String(localized: "guided_reflection_about_ba_title"),
body: String(localized: "guided_reflection_about_ba_body"),
citation: "Martell, C. R., Dimidjian, S., & Herman-Dunn, R. (2010). Behavioral Activation for Depression.",
url: baURL
)
// Disclaimer
Text(String(localized: "guided_reflection_about_disclaimer"))
.font(.footnote)
.foregroundStyle(.tertiary)
.padding(.top, 8)
}
.padding()
}
.navigationTitle(String(localized: "guided_reflection_about_title"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(String(localized: "Done")) {
dismiss()
}
}
}
}
}
// MARK: - Technique Section
@ViewBuilder
private func techniqueSection(
icon: String,
color: Color,
title: String,
body: String,
citation: String,
url: URL
) -> some View {
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 10) {
Image(systemName: icon)
.font(.title3)
.foregroundStyle(color)
.frame(width: 32)
Text(title)
.font(.headline)
}
Text(body)
.font(.subheadline)
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 4) {
Text(citation)
.font(.caption)
.foregroundStyle(.tertiary)
.italic()
Link(destination: url) {
HStack(spacing: 4) {
Text(String(localized: "guided_reflection_about_learn_more"))
Image(systemName: "arrow.up.right")
}
.font(.caption)
}
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.systemGray6))
)
}
}

View File

@@ -20,6 +20,7 @@ struct GuidedReflectionView: View {
@State private var currentStep: Int = 0
@State private var isSaving = false
@State private var showDiscardAlert = false
@State private var showInfoSheet = false
@FocusState private var isTextFieldFocused: Bool
/// Snapshot of the initial state to detect unsaved changes
@@ -83,6 +84,15 @@ struct GuidedReflectionView: View {
.accessibilityIdentifier(AccessibilityID.GuidedReflection.cancelButton)
}
ToolbarItem(placement: .primaryAction) {
Button {
showInfoSheet = true
} label: {
Image(systemName: "info.circle")
}
.accessibilityLabel(String(localized: "guided_reflection_about_title"))
}
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
@@ -102,6 +112,9 @@ struct GuidedReflectionView: View {
Text(String(localized: "guided_reflection_unsaved_message"))
}
.trackScreen(.guidedReflection)
.sheet(isPresented: $showInfoSheet) {
GuidedReflectionInfoView()
}
}
}
@@ -153,6 +166,16 @@ struct GuidedReflectionView: View {
VStack(alignment: .leading, spacing: 16) {
if currentStep < reflection.responses.count {
let response = reflection.responses[currentStep]
let stepLabels = reflection.moodCategory.stepLabels
// CBT step label
if currentStep < stepLabels.count {
Text(stepLabels[currentStep].uppercased())
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.secondary)
.tracking(1.5)
}
Text(response.question)
.font(.title3)