diff --git a/Andromida/Andromida.entitlements b/Andromida/Andromida.entitlements new file mode 100644 index 0000000..c280ba7 --- /dev/null +++ b/Andromida/Andromida.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.icloud-container-identifiers + + com.apple.developer.ubiquity-kvstore-identifier + $(TeamIdentifierPrefix)$(CFBundleIdentifier) + + diff --git a/Andromida/App/Localization/Localizable.xcstrings b/Andromida/App/Localization/Localizable.xcstrings index af1f669..681d283 100644 --- a/Andromida/App/Localization/Localizable.xcstrings +++ b/Andromida/App/Localization/Localizable.xcstrings @@ -155,6 +155,10 @@ } } }, + "Advanced Insights" : { + "comment" : "Subtitle for a feature in the \"Rituals Pro\" upgrade screen, related to \"Advanced Insights\".", + "isCommentAutoGenerated" : true + }, "Begin a four-week arc" : { "localizations" : { "en" : { @@ -265,8 +269,11 @@ } } }, + "Coming Soon" : { + "comment" : "A label indicating that a feature is coming soon.", + "isCommentAutoGenerated" : true + }, "Completed" : { - "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -310,6 +317,10 @@ } } }, + "Create as many arcs as you need" : { + "comment" : "Subtitle for a feature row in the \"Rituals Pro\" upgrade view, describing the ability to create as many rituals as needed.", + "isCommentAutoGenerated" : true + }, "Create ritual" : { "localizations" : { "en" : { @@ -464,6 +475,10 @@ } } }, + "Deeper analytics on your progress" : { + "comment" : "Subtitle for a feature row in the \"Pro\" upgrade view, describing advanced insights.", + "isCommentAutoGenerated" : true + }, "Done" : { "extractionState" : "stale", "localizations" : { @@ -487,6 +502,14 @@ } } }, + "Double tap to check in" : { + "comment" : "A hint text that appears when a user long-presses a \"Today's Habits\" row, indicating that they can mark a habit as completed by double-tapping it.", + "isCommentAutoGenerated" : true + }, + "Double tap to mark incomplete" : { + "comment" : "A hint text that appears when a user long-presses a \"Today's Habits\" row, explaining that tapping it again will mark the task as incomplete.", + "isCommentAutoGenerated" : true + }, "Double tap to toggle" : { "extractionState" : "stale", "localizations" : { @@ -553,6 +576,9 @@ } } } + }, + "Faster iCloud synchronization" : { + }, "Feel a soft response on check-in" : { "localizations" : { @@ -817,6 +843,9 @@ } } } + }, + "Help us build more features" : { + }, "Hydrate" : { "localizations" : { @@ -1084,7 +1113,6 @@ } }, "Not completed" : { - "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1194,6 +1222,13 @@ } } }, + "Priority Sync" : { + + }, + "Pro features are in development" : { + "comment" : "A description below the button that says that the Pro features are in development.", + "isCommentAutoGenerated" : true + }, "Read 10 pages" : { "localizations" : { "en" : { @@ -1238,6 +1273,10 @@ } } }, + "Reset Onboarding" : { + "comment" : "Title of a navigation row in the Settings view that resets the user's onboarding state.", + "isCommentAutoGenerated" : true + }, "Ritual" : { "localizations" : { "en" : { @@ -1392,6 +1431,14 @@ } } }, + "Rituals Pro" : { + "comment" : "The title of the \"Rituals Pro\" view.", + "isCommentAutoGenerated" : true + }, + "Rituals Pro gives you everything you need to build lasting habits." : { + "comment" : "A description of what \"Rituals Pro\" offers.", + "isCommentAutoGenerated" : true + }, "Settings" : { "localizations" : { "en" : { @@ -1547,6 +1594,10 @@ } } }, + "Support Development" : { + "comment" : "Subtitle for a feature row in the \"Pro\" upgrade view, related to supporting the development of the app.", + "isCommentAutoGenerated" : true + }, "Switch tabs to explore rituals and insights" : { "localizations" : { "en" : { @@ -1817,6 +1868,22 @@ } } }, + "Unlimited Rituals" : { + "comment" : "Description of a feature in the \"Pro\" upgrade section, describing that users can create as many rituals as they need.", + "isCommentAutoGenerated" : true + }, + "Unlock unlimited rituals and more" : { + "comment" : "Title of a navigation link in the \"Rituals Pro\" section of the settings view, leading to the ProUpgradeView.", + "isCommentAutoGenerated" : true + }, + "Unlock Your Full Potential" : { + "comment" : "A heading describing the primary benefit of the Pro upgrade.", + "isCommentAutoGenerated" : true + }, + "Upgrade to Pro" : { + "comment" : "Text for a settings card that allows users to upgrade to the Pro version of the app.", + "isCommentAutoGenerated" : true + }, "Why arcs keep habits grounded" : { "localizations" : { "en" : { @@ -1928,5 +1995,5 @@ } } }, - "version" : "1.0" + "version" : "1.1" } \ No newline at end of file diff --git a/Andromida/App/Views/Settings/ProUpgradeView.swift b/Andromida/App/Views/Settings/ProUpgradeView.swift new file mode 100644 index 0000000..8e5872a --- /dev/null +++ b/Andromida/App/Views/Settings/ProUpgradeView.swift @@ -0,0 +1,129 @@ +import SwiftUI +import Bedrock + +struct ProUpgradeView: View { + var body: some View { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: Design.Spacing.xxLarge) { + headerSection + + featuresSection + + comingSoonSection + } + .padding(Design.Spacing.large) + } + .background(AppSurface.primary) + .navigationTitle(String(localized: "Rituals Pro")) + .navigationBarTitleDisplayMode(.inline) + } + + private var headerSection: some View { + VStack(spacing: Design.Spacing.medium) { + Image(systemName: "crown.fill") + .font(.system(size: Design.BaseFontSize.title)) + .foregroundStyle(AppAccent.primary) + .accessibilityHidden(true) + + Text(String(localized: "Unlock Your Full Potential")) + .font(.title2) + .bold() + .foregroundStyle(AppTextColors.primary) + .multilineTextAlignment(.center) + + Text(String(localized: "Rituals Pro gives you everything you need to build lasting habits.")) + .font(.body) + .foregroundStyle(AppTextColors.secondary) + .multilineTextAlignment(.center) + } + .padding(.top, Design.Spacing.large) + } + + private var featuresSection: some View { + VStack(alignment: .leading, spacing: Design.Spacing.medium) { + ProFeatureRowView( + icon: "infinity", + title: String(localized: "Unlimited Rituals"), + subtitle: String(localized: "Create as many arcs as you need") + ) + + ProFeatureRowView( + icon: "chart.line.uptrend.xyaxis", + title: String(localized: "Advanced Insights"), + subtitle: String(localized: "Deeper analytics on your progress") + ) + + ProFeatureRowView( + icon: "icloud.fill", + title: String(localized: "Priority Sync"), + subtitle: String(localized: "Faster iCloud synchronization") + ) + + ProFeatureRowView( + icon: "heart.fill", + title: String(localized: "Support Development"), + subtitle: String(localized: "Help us build more features") + ) + } + .padding(Design.Spacing.large) + .background(AppSurface.card) + .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) + } + + private var comingSoonSection: some View { + VStack(spacing: Design.Spacing.medium) { + Button { + // No action - coming soon + } label: { + Text(String(localized: "Coming Soon")) + .font(.headline) + .foregroundStyle(AppTextColors.disabled) + .frame(maxWidth: .infinity) + .frame(height: AppMetrics.Size.buttonHeight) + .background(AppSurface.tertiary) + .clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) + } + .disabled(true) + + Text(String(localized: "Pro features are in development")) + .font(.caption) + .foregroundStyle(AppTextColors.secondary) + } + } +} + +private struct ProFeatureRowView: View { + let icon: String + let title: String + let subtitle: String + + var body: some View { + HStack(spacing: Design.Spacing.medium) { + Image(systemName: icon) + .font(.title3) + .foregroundStyle(AppAccent.primary) + .frame(width: AppMetrics.Size.iconLarge) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) { + Text(title) + .font(.subheadline) + .bold() + .foregroundStyle(AppTextColors.primary) + + Text(subtitle) + .font(.caption) + .foregroundStyle(AppTextColors.secondary) + } + + Spacer(minLength: Design.Spacing.small) + } + .accessibilityElement(children: .combine) + } +} + +#Preview { + NavigationStack { + ProUpgradeView() + } +} diff --git a/Andromida/App/Views/Settings/SettingsView.swift b/Andromida/App/Views/Settings/SettingsView.swift index 5fe807d..187c130 100644 --- a/Andromida/App/Views/Settings/SettingsView.swift +++ b/Andromida/App/Views/Settings/SettingsView.swift @@ -38,6 +38,22 @@ struct SettingsView: View { ) } + SettingsSectionHeader( + title: String(localized: "Rituals Pro"), + systemImage: "crown.fill", + accentColor: AppAccent.secondary + ) + + SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { + SettingsNavigationRow( + title: String(localized: "Upgrade to Pro"), + subtitle: String(localized: "Unlock unlimited rituals and more"), + backgroundColor: AppSurface.primary + ) { + ProUpgradeView() + } + } + SettingsSectionHeader( title: String(localized: "Ritual pacing"), systemImage: "timer", @@ -124,6 +140,14 @@ struct SettingsView: View { appName: "Rituals" ) } + + SettingsRow( + systemImage: "arrow.counterclockwise", + title: String(localized: "Reset Onboarding"), + iconColor: AppStatus.warning + ) { + UserDefaults.standard.removeObject(forKey: "hasCompletedOnboarding") + } } #endif diff --git a/Andromida/App/Views/Today/Components/TodayHabitRowView.swift b/Andromida/App/Views/Today/Components/TodayHabitRowView.swift index 5873bea..cf4553c 100644 --- a/Andromida/App/Views/Today/Components/TodayHabitRowView.swift +++ b/Andromida/App/Views/Today/Components/TodayHabitRowView.swift @@ -45,6 +45,8 @@ struct TodayHabitRowView: View { .clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) } .accessibilityLabel(Text(title)) + .accessibilityValue(isCompleted ? String(localized: "Completed") : String(localized: "Not completed")) + .accessibilityHint(isCompleted ? String(localized: "Double tap to mark incomplete") : String(localized: "Double tap to check in")) .buttonStyle(.plain) } } diff --git a/Andromida/App/Views/Today/Components/TodayRitualSectionView.swift b/Andromida/App/Views/Today/Components/TodayRitualSectionView.swift index 7d2fac5..b7b648c 100644 --- a/Andromida/App/Views/Today/Components/TodayRitualSectionView.swift +++ b/Andromida/App/Views/Today/Components/TodayRitualSectionView.swift @@ -1,5 +1,6 @@ import SwiftUI import Bedrock +import Sherpa struct HabitRowModel: Identifiable { let id: UUID @@ -18,34 +19,52 @@ struct TodayRitualSectionView: View { let habitRows: [HabitRowModel] var body: some View { - VStack(alignment: .leading, spacing: Design.Spacing.large) { + VStack(alignment: .leading, spacing: Bedrock.Design.Spacing.large) { SectionHeaderView( title: String(localized: "Focus ritual"), subtitle: String(localized: "Four-week arc in progress") ) - RitualFocusCardView( - title: focusTitle, - theme: focusTheme, - dayLabel: dayLabel, - completionSummary: completionSummary, - progress: progress - ) + focusCard SectionHeaderView( title: String(localized: "Habits"), subtitle: String(localized: "Tap to check in") ) - VStack(spacing: Design.Spacing.medium) { - ForEach(habitRows) { habit in - TodayHabitRowView( - title: habit.title, - symbolName: habit.symbolName, - isCompleted: habit.isCompleted, - action: habit.action - ) - } + habitsList + } + } + + private var focusCard: some View { + RitualFocusCardView( + title: focusTitle, + theme: focusTheme, + dayLabel: dayLabel, + completionSummary: completionSummary, + progress: progress + ) + .sherpaTag(RitualsOnboardingTag.focusRitual) + } + + private var habitsList: some View { + VStack(spacing: Bedrock.Design.Spacing.medium) { + if let firstHabit = habitRows.first { + TodayHabitRowView( + title: firstHabit.title, + symbolName: firstHabit.symbolName, + isCompleted: firstHabit.isCompleted, + action: firstHabit.action + ) + .sherpaTag(RitualsOnboardingTag.firstHabit) + } + ForEach(habitRows.dropFirst()) { habit in + TodayHabitRowView( + title: habit.title, + symbolName: habit.symbolName, + isCompleted: habit.isCompleted, + action: habit.action + ) } } } diff --git a/TODO.md b/TODO.md index c995056..b696b3f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,31 +1,31 @@ # Andromida – Focus & Fix List ## 1) Onboarding walkthrough (Sherpa) -- [ ] Restore Sherpa tags for focus ritual card and first habit row without triggering Swift compiler crashes. -- [ ] Confirm walkthrough starts on first launch (ensure `hasCompletedOnboarding` is false in `@AppStorage`). -- [ ] Add a debug-only “Reset Onboarding” action in Settings to clear `hasCompletedOnboarding`. -- [ ] Verify tags visually align with the intended UI elements on iPhone 17 Pro Max. +- [x] Restore Sherpa tags for focus ritual card and first habit row without triggering Swift compiler crashes. +- [x] Confirm walkthrough starts on first launch (ensure `hasCompletedOnboarding` is false in `@AppStorage`). +- [x] Add a debug-only "Reset Onboarding" action in Settings to clear `hasCompletedOnboarding`. +- [x] Verify tags visually align with the intended UI elements on iPhone 17 Pro Max. ## 2) Swift compiler stability -- [ ] Identify the minimal Sherpa usage pattern that avoids the “failed to produce diagnostic” crash. -- [ ] Avoid `#Preview` macro ambiguity when Sherpa is imported (use `#if DEBUG` + `PreviewProvider` or remove previews for Sherpa-tagged views). -- [ ] Avoid ambiguous accessibility modifier overloads when Sherpa is imported. +- [x] Identify the minimal Sherpa usage pattern that avoids the "failed to produce diagnostic" crash. +- [x] Avoid `#Preview` macro ambiguity when Sherpa is imported (use `#if DEBUG` + `PreviewProvider` or remove previews for Sherpa-tagged views). +- [x] Avoid ambiguous accessibility modifier overloads when Sherpa is imported. ## 3) Today tab UX polish -- [ ] Re-add accessibility value/hint for habit rows once Sherpa-related ambiguity is resolved. -- [ ] Confirm focus ritual card and habit rows still match the intended visual hierarchy after refactors. +- [x] Re-add accessibility value/hint for habit rows once Sherpa-related ambiguity is resolved. +- [x] Confirm focus ritual card and habit rows still match the intended visual hierarchy after refactors. ## 4) Settings & product readiness -- [ ] Add a paid-app placeholder (e.g., “Pro unlock” copy) without backend requirements. +- [x] Add a paid-app placeholder (e.g., "Pro unlock" copy) without backend requirements. - [ ] Confirm default settings and theme in Settings match Bedrock branding. ## 5) Data & defaults - [ ] Confirm seed ritual creation and quick ritual creation behave as expected. -- [ ] Validate SwiftData sync (if enabled) doesn’t require any external API. +- [ ] Validate SwiftData sync (if enabled) doesn't require any external API. ## 6) QA checklist -- [ ] First-launch walkthrough appears on a clean install. -- [ ] Onboarding can be manually reset from Settings. -- [ ] No build warnings or Swift compiler crashes. -- [ ] iPhone 17 Pro Max simulator layout verified on Today, Rituals, Insights, Settings. +- [x] First-launch walkthrough appears on a clean install. +- [x] Onboarding can be manually reset from Settings. +- [x] No build warnings or Swift compiler crashes. +- [x] iPhone 17 Pro Max simulator layout verified on Today, Rituals, Insights, Settings.