Files
Reflect/Shared/Views/InsightsView/WeeklyDigestCardView.swift
Trey t 329fb7c671 Remove #if DEBUG guards for TestFlight, polish weekly digest and insights UX
- Remove #if DEBUG from all debug settings, exporters, and IAP bypass so
  debug options are available in TestFlight builds
- Weekly digest card: replace dismiss X with collapsible chevron caret
- Weekly digest: generate on-demand when opening Insights tab if no cached
  digest exists (BGTask + notification kept as bonus path)
- Fix digest intention text color (was .secondary, now uses theme textColor)
- Add "Generate Weekly Digest" debug button in Settings
- Add generating overlay on Insights tab with pulsing sparkles icon that
  stays visible until all sections finish loading (content at 0.2 opacity)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:15:23 -05:00

139 lines
5.0 KiB
Swift

//
// WeeklyDigestCardView.swift
// Reflect
//
// Displays the AI-generated weekly emotional digest card in the Insights tab.
//
import SwiftUI
struct WeeklyDigestCardView: View {
let digest: WeeklyDigest
@AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
private var textColor: Color { theme.currentTheme.labelColor }
private var accentColor: Color { moodTint.color(forMood: .good) }
@State private var isExpanded = true
@State private var appeared = false
var body: some View {
VStack(alignment: .leading, spacing: 0) {
// Header always visible, tappable to toggle
Button {
withAnimation(.easeInOut(duration: 0.25)) {
isExpanded.toggle()
}
} label: {
HStack {
Image(systemName: digest.iconName)
.font(.title2)
.foregroundStyle(accentColor)
VStack(alignment: .leading, spacing: 2) {
Text(String(localized: "Weekly Digest"))
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.secondary)
.textCase(.uppercase)
Text(digest.headline)
.font(.headline)
.foregroundColor(textColor)
.multilineTextAlignment(.leading)
}
Spacer()
Image(systemName: "chevron.up")
.font(.caption.weight(.semibold))
.foregroundStyle(.secondary)
.rotationEffect(.degrees(isExpanded ? 0 : 180))
}
}
.buttonStyle(.plain)
.accessibilityIdentifier(AccessibilityID.WeeklyDigest.dismissButton)
// Expandable content
if isExpanded {
VStack(alignment: .leading, spacing: 16) {
// Summary
Text(digest.summary)
.font(.subheadline)
.foregroundColor(textColor)
.fixedSize(horizontal: false, vertical: true)
Divider()
// Highlight
HStack(alignment: .top, spacing: 10) {
Image(systemName: "star.fill")
.font(.caption)
.foregroundStyle(.yellow)
.padding(.top, 2)
Text(digest.highlight)
.font(.subheadline)
.foregroundColor(textColor)
.fixedSize(horizontal: false, vertical: true)
}
// Intention
HStack(alignment: .top, spacing: 10) {
Image(systemName: "arrow.right.circle.fill")
.font(.caption)
.foregroundStyle(accentColor)
.padding(.top, 2)
Text(digest.intention)
.font(.subheadline)
.foregroundColor(textColor)
.fixedSize(horizontal: false, vertical: true)
}
// Date range
Text(dateRangeString)
.font(.caption2)
.foregroundStyle(.tertiary)
}
.padding(.top, 16)
.transition(.opacity.combined(with: .move(edge: .top)))
}
}
.padding(20)
.background(
RoundedRectangle(cornerRadius: 20)
.fill(Color(.secondarySystemBackground))
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(
LinearGradient(
colors: [accentColor.opacity(0.3), .purple.opacity(0.2)],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
lineWidth: 1
)
)
)
.padding(.horizontal)
.opacity(appeared ? 1 : 0)
.offset(y: appeared ? 0 : 10)
.onAppear {
withAnimation(.easeOut(duration: 0.4)) {
appeared = true
}
}
.accessibilityIdentifier(AccessibilityID.WeeklyDigest.card)
}
private var dateRangeString: String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return "\(formatter.string(from: digest.weekStartDate)) - \(formatter.string(from: digest.weekEndDate))"
}
}