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>
This commit is contained in:
Trey t
2026-04-04 11:15:23 -05:00
parent ab8d8fbdc0
commit 329fb7c671
16 changed files with 232 additions and 117 deletions

View File

@@ -84,6 +84,10 @@ struct InsightsView: View {
if iapManager.shouldShowPaywall {
paywallOverlay
}
if selectedTab == .insights && isGeneratingInsights && !iapManager.shouldShowPaywall {
generatingOverlay
}
}
}
.sheet(isPresented: $showSubscriptionStore) {
@@ -104,12 +108,22 @@ struct InsightsView: View {
// MARK: - Insights Content
private func loadWeeklyDigest() {
if #available(iOS 26, *), !iapManager.shouldShowPaywall {
if let digest = FoundationModelsDigestService.shared.loadLatestDigest(),
digest.isFromCurrentWeek,
!WeeklyDigest.isDismissed(for: digest) {
guard #available(iOS 26, *), !iapManager.shouldShowPaywall else { return }
// Try cached digest first
if let digest = FoundationModelsDigestService.shared.loadLatestDigest(),
digest.isFromCurrentWeek {
weeklyDigest = digest
return
}
// No digest for this week generate one on-demand
Task {
do {
let digest = try await FoundationModelsDigestService.shared.generateWeeklyDigest()
weeklyDigest = digest
showDigest = true
} catch {
// Not enough data or AI unavailable just don't show the card
}
}
}
@@ -118,10 +132,8 @@ struct InsightsView: View {
ScrollView {
VStack(spacing: 20) {
// Weekly Digest Card
if showDigest, let digest = weeklyDigest {
WeeklyDigestCardView(digest: digest) {
showDigest = false
}
if let digest = weeklyDigest {
WeeklyDigestCardView(digest: digest)
}
// This Month Section
@@ -166,6 +178,8 @@ struct InsightsView: View {
.padding(.vertical)
.padding(.bottom, 100)
}
.opacity(isGeneratingInsights && !iapManager.shouldShowPaywall ? 0.2 : 1.0)
.animation(.easeInOut(duration: 0.3), value: isGeneratingInsights)
.refreshable {
viewModel.refreshInsights()
// Small delay to show refresh animation
@@ -174,6 +188,50 @@ struct InsightsView: View {
.disabled(iapManager.shouldShowPaywall)
}
// MARK: - Generating State
private var isGeneratingInsights: Bool {
let states = [viewModel.monthLoadingState, viewModel.yearLoadingState, viewModel.allTimeLoadingState]
return states.contains(where: { $0 == .loading || $0 == .idle })
}
private var generatingOverlay: some View {
VStack(spacing: 20) {
Spacer()
VStack(spacing: 16) {
Image(systemName: "sparkles")
.font(.system(size: 36))
.foregroundStyle(
LinearGradient(
colors: [.purple, .blue],
startPoint: .leading,
endPoint: .trailing
)
)
.symbolEffect(.pulse, options: .repeating)
Text(String(localized: "Generating Insights"))
.font(.headline)
.foregroundColor(textColor)
Text(String(localized: "Apple Intelligence is analyzing your mood data..."))
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
.padding(32)
.background(
RoundedRectangle(cornerRadius: 24)
.fill(.regularMaterial)
)
.padding(.horizontal, 40)
Spacer()
}
.transition(.opacity)
}
// MARK: - Paywall Overlay
private var paywallOverlay: some View {