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

@@ -3275,6 +3275,10 @@
} }
} }
}, },
"Apple Intelligence is analyzing your mood data..." : {
"comment" : "A description of the process of generating insights.",
"isCommentAutoGenerated" : true
},
"Apple Intelligence Required" : { "Apple Intelligence Required" : {
"comment" : "A title for a card that informs the user that AI report generation requires Apple Intelligence to be enabled in Settings.", "comment" : "A title for a card that informs the user that AI report generation requires Apple Intelligence to be enabled in Settings.",
"extractionState" : "stale", "extractionState" : "stale",
@@ -4672,6 +4676,10 @@
} }
} }
}, },
"Create AI digest now (shows in Insights tab)" : {
"comment" : "A description of the action of generating a digest.",
"isCommentAutoGenerated" : true
},
"Create random icons" : { "Create random icons" : {
"localizations" : { "localizations" : {
"de" : { "de" : {
@@ -9035,6 +9043,10 @@
} }
} }
}, },
"Generate Weekly Digest" : {
"comment" : "A button that generates a weekly digest of your app's usage.",
"isCommentAutoGenerated" : true
},
"Generating AI summary..." : { "Generating AI summary..." : {
"comment" : "Text displayed in the progress indicator while generating the AI-generated quick summary report.", "comment" : "Text displayed in the progress indicator while generating the AI-generated quick summary report.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@@ -9077,6 +9089,10 @@
} }
} }
}, },
"Generating Insights" : {
"comment" : "A message displayed when the app is generating user insights.",
"isCommentAutoGenerated" : true
},
"Generating monthly summaries..." : { "Generating monthly summaries..." : {
"comment" : "Message displayed in the progress view while generating monthly summaries for the detailed report.", "comment" : "Message displayed in the progress view while generating monthly summaries for the detailed report.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@@ -9465,6 +9481,10 @@
} }
} }
}, },
"Great job completing your reflection. Taking time to check in with yourself is a powerful habit." : {
"comment" : "A fallback message displayed when the AI is unavailable.",
"isCommentAutoGenerated" : true
},
"Green dot = eligible to show. Tips only show once per session when eligible." : { "Green dot = eligible to show. Tips only show once per session when eligible." : {
"comment" : "A footer label explaining that tips are only shown once per session and that the green dot indicates whether a tip is currently eligible to be shown.", "comment" : "A footer label explaining that tips are only shown once per session and that the green dot indicates whether a tip is currently eligible to be shown.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@@ -26903,6 +26923,10 @@
} }
} }
}, },
"Weekly Digest" : {
"comment" : "A label displayed above the weekly digest.",
"isCommentAutoGenerated" : true
},
"WeekTotalTemplate body" : { "WeekTotalTemplate body" : {
"comment" : "The body text for the WeekTotalTemplate view.", "comment" : "The body text for the WeekTotalTemplate view.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@@ -27864,6 +27888,14 @@
} }
} }
} }
},
"Your Reflection" : {
"comment" : "A title describing the reflection.",
"isCommentAutoGenerated" : true
},
"Your Weekly Digest" : {
"comment" : "Title of a notification that appears weekly.",
"isCommentAutoGenerated" : true
} }
}, },
"version" : "1.1" "version" : "1.1"

View File

@@ -37,13 +37,9 @@ class IAPManager: ObservableObject {
/// Set to `true` to bypass all subscription checks and grant full access (for development only) /// Set to `true` to bypass all subscription checks and grant full access (for development only)
/// Togglable at runtime in DEBUG builds via Settings > Debug > Bypass Subscription /// Togglable at runtime in DEBUG builds via Settings > Debug > Bypass Subscription
#if DEBUG
@Published var bypassSubscription: Bool { @Published var bypassSubscription: Bool {
didSet { UserDefaults.standard.set(bypassSubscription, forKey: "debug_bypassSubscription") } didSet { UserDefaults.standard.set(bypassSubscription, forKey: "debug_bypassSubscription") }
} }
#else
let bypassSubscription = false
#endif
// MARK: - Constants // MARK: - Constants
@@ -140,9 +136,7 @@ class IAPManager: ObservableObject {
// MARK: - Initialization // MARK: - Initialization
init() { init() {
#if DEBUG
self.bypassSubscription = UserDefaults.standard.bool(forKey: "debug_bypassSubscription") self.bypassSubscription = UserDefaults.standard.bool(forKey: "debug_bypassSubscription")
#endif
restoreCachedSubscriptionState() restoreCachedSubscriptionState()
updateListenerTask = listenForTransactions() updateListenerTask = listenForTransactions()
@@ -365,7 +359,6 @@ class IAPManager: ObservableObject {
return false return false
} }
#if DEBUG
/// Reset subscription state for UI testing. Called after group defaults are cleared /// Reset subscription state for UI testing. Called after group defaults are cleared
/// so that stale cached state from previous test runs is discarded. /// so that stale cached state from previous test runs is discarded.
func resetForTesting() { func resetForTesting() {
@@ -382,7 +375,6 @@ class IAPManager: ObservableObject {
updateTrialState() updateTrialState()
} }
#endif
private func updateTrialState() { private func updateTrialState() {
let daysSinceInstall = Calendar.current.dateComponents([.day], from: firstLaunchDate, to: Date()).day ?? 0 let daysSinceInstall = Calendar.current.dateComponents([.day], from: firstLaunchDate, to: Date()).day ?? 0

View File

@@ -157,7 +157,6 @@ class LocalNotification {
} }
} }
#if DEBUG
/// Sends one notification from each personality pack, staggered over 10 seconds for screenshot /// Sends one notification from each personality pack, staggered over 10 seconds for screenshot
public class func sendAllPersonalityNotificationsForScreenshot() { public class func sendAllPersonalityNotificationsForScreenshot() {
let _ = createNotificationCategory() let _ = createNotificationCategory()
@@ -195,5 +194,4 @@ class LocalNotification {
} }
} }
} }
#endif
} }

View File

@@ -26,7 +26,6 @@ extension DataController {
} }
func populateMemory() { func populateMemory() {
#if DEBUG
for idx in 1..<255 { for idx in 1..<255 {
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())! let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
var moodValue = Int.random(in: 2...4) var moodValue = Int.random(in: 2...4)
@@ -43,7 +42,6 @@ extension DataController {
modelContext.insert(entry) modelContext.insert(entry)
} }
save() save()
#endif
} }
/// Creates an entry that is NOT inserted into the context - used for UI placeholders /// Creates an entry that is NOT inserted into the context - used for UI placeholders
@@ -79,7 +77,6 @@ extension DataController {
saveAndRunDataListeners() saveAndRunDataListeners()
} }
#if DEBUG
func populate2YearsData() { func populate2YearsData() {
clearDB() clearDB()
@@ -100,7 +97,6 @@ extension DataController {
saveAndRunDataListeners() saveAndRunDataListeners()
} }
#endif
private static func randomMood() -> Mood { private static func randomMood() -> Mood {
var moodValue = Int.random(in: 3...4) var moodValue = Int.random(in: 3...4)

View File

@@ -4,8 +4,6 @@
// //
// Exportable insights views with sample AI-generated insights for screenshots. // Exportable insights views with sample AI-generated insights for screenshots.
// //
#if DEBUG
import SwiftUI import SwiftUI
// MARK: - Sample Insights Data // MARK: - Sample Insights Data
@@ -377,4 +375,3 @@ struct ExportableInsightsContainer<Content: View>: View {
.background(backgroundColor) .background(backgroundColor)
} }
} }
#endif

View File

@@ -5,8 +5,6 @@
// Exportable watch views that match the real watchOS layouts. // Exportable watch views that match the real watchOS layouts.
// These views accept tint/icon configuration as parameters for batch export. // These views accept tint/icon configuration as parameters for batch export.
// //
#if DEBUG
import SwiftUI import SwiftUI
// MARK: - Watch Export Configuration // MARK: - Watch Export Configuration
@@ -362,4 +360,3 @@ struct ExportableComplicationContainer<Content: View>: View {
.clipShape(isCircular ? AnyShape(Circle()) : AnyShape(RoundedRectangle(cornerRadius: 12, style: .continuous))) .clipShape(isCircular ? AnyShape(Circle()) : AnyShape(RoundedRectangle(cornerRadius: 12, style: .continuous)))
} }
} }
#endif

View File

@@ -5,8 +5,6 @@
// Exportable widget views that match the real WidgetKit widgets pixel-for-pixel. // Exportable widget views that match the real WidgetKit widgets pixel-for-pixel.
// These views accept tint/icon configuration as parameters for batch export. // These views accept tint/icon configuration as parameters for batch export.
// //
#if DEBUG
import SwiftUI import SwiftUI
// MARK: - Widget Theme Configuration // MARK: - Widget Theme Configuration
@@ -691,4 +689,3 @@ struct ExportableWidgetContainer<Content: View>: View {
.clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
} }
} }
#endif

View File

@@ -5,7 +5,6 @@
// Debug utility to export insights view screenshots with sample AI data. // Debug utility to export insights view screenshots with sample AI data.
// //
#if DEBUG
import SwiftUI import SwiftUI
import UIKit import UIKit
@@ -100,4 +99,3 @@ class InsightsExporter {
} }
} }
} }
#endif

View File

@@ -5,7 +5,6 @@
// Debug utility to export sharing template screenshots. // Debug utility to export sharing template screenshots.
// //
#if DEBUG
import SwiftUI import SwiftUI
import UIKit import UIKit
@@ -173,4 +172,3 @@ class SharingScreenshotExporter {
return false return false
} }
} }
#endif

View File

@@ -6,7 +6,6 @@
// Uses the exportable watch views from ExportableWatchViews.swift. // Uses the exportable watch views from ExportableWatchViews.swift.
// //
#if DEBUG
import SwiftUI import SwiftUI
import UIKit import UIKit
@@ -247,4 +246,3 @@ class WatchExporter {
} }
} }
} }
#endif

View File

@@ -6,7 +6,6 @@
// Uses the real widget view layouts from ExportableWidgetViews.swift. // Uses the real widget view layouts from ExportableWidgetViews.swift.
// //
#if DEBUG
import SwiftUI import SwiftUI
import UIKit import UIKit
@@ -389,4 +388,3 @@ class WidgetExporter {
} }
} }
} }
#endif

View File

@@ -84,6 +84,10 @@ struct InsightsView: View {
if iapManager.shouldShowPaywall { if iapManager.shouldShowPaywall {
paywallOverlay paywallOverlay
} }
if selectedTab == .insights && isGeneratingInsights && !iapManager.shouldShowPaywall {
generatingOverlay
}
} }
} }
.sheet(isPresented: $showSubscriptionStore) { .sheet(isPresented: $showSubscriptionStore) {
@@ -104,12 +108,22 @@ struct InsightsView: View {
// MARK: - Insights Content // MARK: - Insights Content
private func loadWeeklyDigest() { private func loadWeeklyDigest() {
if #available(iOS 26, *), !iapManager.shouldShowPaywall { guard #available(iOS 26, *), !iapManager.shouldShowPaywall else { return }
if let digest = FoundationModelsDigestService.shared.loadLatestDigest(),
digest.isFromCurrentWeek, // Try cached digest first
!WeeklyDigest.isDismissed(for: digest) { 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 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 { ScrollView {
VStack(spacing: 20) { VStack(spacing: 20) {
// Weekly Digest Card // Weekly Digest Card
if showDigest, let digest = weeklyDigest { if let digest = weeklyDigest {
WeeklyDigestCardView(digest: digest) { WeeklyDigestCardView(digest: digest)
showDigest = false
}
} }
// This Month Section // This Month Section
@@ -166,6 +178,8 @@ struct InsightsView: View {
.padding(.vertical) .padding(.vertical)
.padding(.bottom, 100) .padding(.bottom, 100)
} }
.opacity(isGeneratingInsights && !iapManager.shouldShowPaywall ? 0.2 : 1.0)
.animation(.easeInOut(duration: 0.3), value: isGeneratingInsights)
.refreshable { .refreshable {
viewModel.refreshInsights() viewModel.refreshInsights()
// Small delay to show refresh animation // Small delay to show refresh animation
@@ -174,6 +188,50 @@ struct InsightsView: View {
.disabled(iapManager.shouldShowPaywall) .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 // MARK: - Paywall Overlay
private var paywallOverlay: some View { private var paywallOverlay: some View {

View File

@@ -10,7 +10,6 @@ import SwiftUI
struct WeeklyDigestCardView: View { struct WeeklyDigestCardView: View {
let digest: WeeklyDigest let digest: WeeklyDigest
let onDismiss: () -> Void
@AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default @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 @AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
@@ -18,82 +17,91 @@ struct WeeklyDigestCardView: View {
private var textColor: Color { theme.currentTheme.labelColor } private var textColor: Color { theme.currentTheme.labelColor }
private var accentColor: Color { moodTint.color(forMood: .good) } private var accentColor: Color { moodTint.color(forMood: .good) }
@State private var isExpanded = true
@State private var appeared = false @State private var appeared = false
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 0) {
// Header // Header always visible, tappable to toggle
HStack { Button {
Image(systemName: digest.iconName) withAnimation(.easeInOut(duration: 0.25)) {
.font(.title2) isExpanded.toggle()
.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)
} }
} label: {
HStack {
Image(systemName: digest.iconName)
.font(.title2)
.foregroundStyle(accentColor)
Spacer() VStack(alignment: .leading, spacing: 2) {
Text(String(localized: "Weekly Digest"))
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.secondary)
.textCase(.uppercase)
Button { Text(digest.headline)
WeeklyDigest.markDismissed() .font(.headline)
withAnimation(.easeInOut(duration: 0.3)) { .foregroundColor(textColor)
onDismiss() .multilineTextAlignment(.leading)
} }
} label: {
Image(systemName: "xmark.circle.fill") Spacer()
.font(.title3)
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) .foregroundStyle(.tertiary)
} }
.accessibilityLabel(String(localized: "Dismiss digest")) .padding(.top, 16)
.accessibilityIdentifier(AccessibilityID.WeeklyDigest.dismissButton) .transition(.opacity.combined(with: .move(edge: .top)))
} }
// 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(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
// Date range
Text(dateRangeString)
.font(.caption2)
.foregroundStyle(.tertiary)
} }
.padding(20) .padding(20)
.background( .background(

View File

@@ -7,7 +7,6 @@
import SwiftUI import SwiftUI
#if DEBUG
struct PaywallPreviewSettingsView: View { struct PaywallPreviewSettingsView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var selectedStyle: PaywallStyle = .celestial @State private var selectedStyle: PaywallStyle = .celestial
@@ -928,4 +927,3 @@ struct JournalMiniPreview: View {
.environmentObject(IAPManager()) .environmentObject(IAPManager())
} }
} }
#endif

View File

@@ -68,7 +68,6 @@ struct SettingsContentView: View {
privacyButton privacyButton
analyticsToggle analyticsToggle
#if DEBUG
// Debug section // Debug section
debugSectionHeader debugSectionHeader
addTestDataButton addTestDataButton
@@ -83,9 +82,9 @@ struct SettingsContentView: View {
exportInsightsButton exportInsightsButton
generateAndExportButton generateAndExportButton
deleteHealthKitDataButton deleteHealthKitDataButton
generateWeeklyDigestButton
clearDataButton clearDataButton
#endif
Spacer() Spacer()
.frame(height: 20) .frame(height: 20)
@@ -207,7 +206,6 @@ struct SettingsContentView: View {
// MARK: - Debug Section // MARK: - Debug Section
#if DEBUG
private var debugSectionHeader: some View { private var debugSectionHeader: some View {
HStack { HStack {
Text("Debug") Text("Debug")
@@ -759,6 +757,63 @@ struct SettingsContentView: View {
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }
@State private var isGeneratingDigest = false
@State private var digestResult: String?
private var generateWeeklyDigestButton: some View {
Button {
isGeneratingDigest = true
digestResult = nil
Task {
if #available(iOS 26, *) {
do {
let digest = try await FoundationModelsDigestService.shared.generateWeeklyDigest()
digestResult = "\(digest.headline)"
} catch {
digestResult = "\(error.localizedDescription)"
}
} else {
digestResult = "✗ Requires iOS 26+"
}
isGeneratingDigest = false
}
} label: {
HStack(spacing: 12) {
if isGeneratingDigest {
ProgressView()
.frame(width: 32)
} else {
Image(systemName: "sparkles.rectangle.stack")
.font(.title2)
.foregroundColor(.purple)
.frame(width: 32)
}
VStack(alignment: .leading, spacing: 2) {
Text("Generate Weekly Digest")
.foregroundColor(textColor)
if let result = digestResult {
Text(result)
.font(.caption)
.foregroundColor(result.contains("") ? .green : .red)
} else {
Text("Create AI digest now (shows in Insights tab)")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
}
.padding()
}
.disabled(isGeneratingDigest)
.background(theme.currentTheme.secondaryBGColor)
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var clearDataButton: some View { private var clearDataButton: some View {
Button { Button {
MoodLogger.shared.deleteAllData() MoodLogger.shared.deleteAllData()
@@ -787,7 +842,6 @@ struct SettingsContentView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
} }
#endif
// MARK: - Privacy Lock Toggle // MARK: - Privacy Lock Toggle
@@ -1394,7 +1448,6 @@ struct SettingsView: View {
// specialThanksCell // specialThanksCell
} }
#if DEBUG
Group { Group {
Divider() Divider()
Text("Test builds only") Text("Test builds only")
@@ -1410,7 +1463,6 @@ struct SettingsView: View {
Divider() Divider()
} }
Spacer() Spacer()
#endif
Text("\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))") Text("\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))")
.font(.body) .font(.body)
} }

View File

@@ -246,7 +246,6 @@ extension View {
// MARK: - Tips Preview View (Debug) // MARK: - Tips Preview View (Debug)
#if DEBUG
struct TipsPreviewView: View { struct TipsPreviewView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var selectedTipIndex: Int? @State private var selectedTipIndex: Int?
@@ -384,4 +383,3 @@ private struct TipIndexWrapper: Identifiable {
TipsPreviewView() TipsPreviewView()
} }
} }
#endif