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:
File diff suppressed because it is too large
Load Diff
@@ -10,9 +10,9 @@ import Foundation
|
|||||||
// MARK: - Mood Category
|
// MARK: - Mood Category
|
||||||
|
|
||||||
enum MoodCategory: String, Codable {
|
enum MoodCategory: String, Codable {
|
||||||
case positive // great, good → 3 questions
|
case positive // great, good → 3 questions (Behavioral Activation)
|
||||||
case neutral // average → 4 questions
|
case neutral // average → 4 questions (ACT Cognitive Defusion)
|
||||||
case negative // bad, horrible → 4 questions
|
case negative // bad, horrible → 4 questions (CBT Thought Record)
|
||||||
|
|
||||||
init(from mood: Mood) {
|
init(from mood: Mood) {
|
||||||
switch mood {
|
switch mood {
|
||||||
@@ -29,6 +29,41 @@ enum MoodCategory: String, Codable {
|
|||||||
case .neutral, .negative: return 4
|
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
|
// MARK: - Guided Reflection
|
||||||
|
|||||||
124
Shared/Views/GuidedReflectionInfoView.swift
Normal file
124
Shared/Views/GuidedReflectionInfoView.swift
Normal 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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ struct GuidedReflectionView: View {
|
|||||||
@State private var currentStep: Int = 0
|
@State private var currentStep: Int = 0
|
||||||
@State private var isSaving = false
|
@State private var isSaving = false
|
||||||
@State private var showDiscardAlert = false
|
@State private var showDiscardAlert = false
|
||||||
|
@State private var showInfoSheet = false
|
||||||
@FocusState private var isTextFieldFocused: Bool
|
@FocusState private var isTextFieldFocused: Bool
|
||||||
|
|
||||||
/// Snapshot of the initial state to detect unsaved changes
|
/// Snapshot of the initial state to detect unsaved changes
|
||||||
@@ -83,6 +84,15 @@ struct GuidedReflectionView: View {
|
|||||||
.accessibilityIdentifier(AccessibilityID.GuidedReflection.cancelButton)
|
.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) {
|
ToolbarItemGroup(placement: .keyboard) {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Done") {
|
Button("Done") {
|
||||||
@@ -102,6 +112,9 @@ struct GuidedReflectionView: View {
|
|||||||
Text(String(localized: "guided_reflection_unsaved_message"))
|
Text(String(localized: "guided_reflection_unsaved_message"))
|
||||||
}
|
}
|
||||||
.trackScreen(.guidedReflection)
|
.trackScreen(.guidedReflection)
|
||||||
|
.sheet(isPresented: $showInfoSheet) {
|
||||||
|
GuidedReflectionInfoView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +166,16 @@ struct GuidedReflectionView: View {
|
|||||||
VStack(alignment: .leading, spacing: 16) {
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
if currentStep < reflection.responses.count {
|
if currentStep < reflection.responses.count {
|
||||||
let response = reflection.responses[currentStep]
|
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)
|
Text(response.question)
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
|
|||||||
Reference in New Issue
Block a user