Conversation
- Onboarding flow + Selector-based first-launch routing in main.dart - 5 new screens: add_habit, analytics, onboarding, premium, settings - AdService (banner-only, --dart-define gated, premium-aware) - OnboardingService (settings-box backed, no new HiveType) - 5 new test files (30 pass, 2 documented skips) - Android: AGP/Gradle/desugaring + USE_EXACT_ALARM + minSdk 26 - iOS: Info.plist privacy strings + Time Sensitive Notifications hint - pubspec: add google_mobile_ads ^5.3.1; description -> Invisible Habit Builder Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR catches the Flutter app up to the intended v1.0 “store-ready” state by wiring first-launch onboarding, new product screens, monetization hooks, and mobile platform configuration into the existing local-first habit-tracking codebase.
Changes:
- Adds new user-facing flows and screens for onboarding, habit creation, analytics, premium, and settings.
- Wires new services for onboarding state and banner ads into
main.dart, and extends the home screen with navigation, gating, and ad/reminder behavior. - Updates mobile platform config, dependencies, docs, and tests to support the v1.0 release surface.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
test/onboarding_routing_test.dart |
Adds widget coverage for onboarding route selection. |
test/graceful_degradation_test.dart |
Verifies app boot with unset --dart-define values. |
test/analytics_screen_test.dart |
Adds skipped analytics empty-state widget test. |
test/add_habit_screen_test.dart |
Adds widget tests for add-habit validation/submit flow. |
test/ad_service_gating_test.dart |
Adds unit coverage for ad-service gating behavior. |
pubspec.yaml |
Renames app description and adds AdMob dependency. |
pubspec.lock |
Locks new package graph including AdMob transitive deps. |
lib/services/onboarding_service.dart |
Introduces persisted onboarding completion state. |
lib/services/ad_service.dart |
Adds banner-ad service with config/premium gating. |
lib/screens/settings_screen.dart |
Adds multi-section settings UI for theme, health, data, and premium. |
lib/screens/premium_screen.dart |
Adds lifetime IAP paywall screen. |
lib/screens/onboarding_screen.dart |
Adds four-step onboarding flow and permission prompts. |
lib/screens/home_screen.dart |
Converts home to stateful, adds nav, banner slot, and reminder re-arm. |
lib/screens/analytics_screen.dart |
Adds analytics heatmap, premium upsell, and IF-THEN plan flow. |
lib/screens/add_habit_screen.dart |
Adds full create/edit habit form and reminder scheduling. |
lib/main.dart |
Wires new services/providers and first-launch routing. |
ios/Runner/Info.plist |
Updates iOS app name and adds notification/health/AdMob metadata. |
CLAUDE.md |
Updates project memory/state for the new v1.0 surface. |
android/app/src/main/AndroidManifest.xml |
Adds reminder, network, health, and AdMob manifest entries. |
android/app/build.gradle.kts |
Adds AdMob manifest placeholder configuration. |
Comment on lines
+120
to
+123
| 'digest.', | ||
| primaryAction: FilledButton( | ||
| onPressed: _requestNotifications, | ||
| child: const Text('Allow notifications'), |
Comment on lines
+132
to
+139
| title: 'Optional: Apple Health / Health Connect', | ||
| body: | ||
| 'Habit completions can write to your platform Health ' | ||
| 'app so they show up in your Activity & Mindfulness ' | ||
| 'rings. Nothing ever leaves your device.', | ||
| primaryAction: FilledButton( | ||
| onPressed: _requestHealth, | ||
| child: const Text('Enable health write-back'), |
Comment on lines
+51
to
+53
| for (final habit in provider.activeHabits) { | ||
| final when = _nextOccurrence(habit, now); | ||
| await notifications.scheduleHabitReminder(habit: habit, when: when); |
Comment on lines
+424
to
+433
|
|
||
| @override | ||
| void didChangeDependencies() { | ||
| super.didChangeDependencies(); | ||
| final ads = context.read<AdService>(); | ||
| if (_banner == null && !ads.adsRemoved) { | ||
| final banner = ads.createBanner(); | ||
| banner?.load(); | ||
| setState(() => _banner = banner); | ||
| } |
Comment on lines
+148
to
+187
| return ListView.separated( | ||
| padding: const EdgeInsets.fromLTRB(16, 8, 16, 96), | ||
| itemCount: habits.length, | ||
| separatorBuilder: (_, _) => const SizedBox(height: 12), | ||
| itemBuilder: (context, index) { | ||
| final habit = habits[index]; | ||
| final completed = provider.isCompletedOn(habit.id, today); | ||
| final streak = provider.streakFor(habit.id); | ||
| // Friction-reduction: on a flagged high-skip-risk day with a | ||
| // configured 2-min version, surface the shrunken form | ||
| // (Clear's 2-min rule + Wood's context-over-willpower). | ||
| final pattern = skipPatterns.current(habit.id); | ||
| final isHighRisk = pattern.isHighRiskAt(today); | ||
| final version = provider.surfaceVersionFor( | ||
| habit, | ||
| date: today, | ||
| isHighSkipRisk: isHighRisk, | ||
| ); | ||
| final displayName = | ||
| version == HabitVersionToSurface.twoMinute | ||
| ? habit.twoMinuteVersion | ||
| : habit.name; | ||
| return _HabitCard( | ||
| habit: habit, | ||
| displayName: displayName, | ||
| showShrunkBadge: | ||
| version == HabitVersionToSurface.twoMinute, | ||
| completed: completed, | ||
| streakLabel: streak.label, | ||
| streakState: streak.state, | ||
| onToggle: () async { | ||
| final nowCompleted = await provider.toggleCompletion( | ||
| habit.id, | ||
| wasTwoMinuteVersion: | ||
| version == HabitVersionToSurface.twoMinute, | ||
| ); | ||
| // Pavlovian celebration only on completion (not un-toggle). | ||
| if (nowCompleted) { | ||
| await HapticFeedback.lightImpact(); | ||
| } |
Comment on lines
+258
to
+262
| await intentions.commit( | ||
| proposal: proposal, | ||
| whenClause: whenText, | ||
| thenClause: thenText, | ||
| ); |
| <key>NSHealthUpdateUsageDescription</key> | ||
| <string>Invisible Habit Builder writes your habit completions to Apple Health so they appear in your Activity and Mindfulness rings.</string> | ||
| <key>GADApplicationIdentifier</key> | ||
| <string>$(GAD_APPLICATION_IDENTIFIER)</string> |
| uuid: ^4.5.1 # stable habit IDs | ||
| collection: ^1.19.1 # groupBy, partition for skip-pattern math | ||
| path_provider: ^2.1.5 # Hive box + delivery-audit log location | ||
| google_mobile_ads: ^5.3.1 # Banner ads for free tier; AdService gracefully no-ops when unit IDs unset. |
| content: Text( | ||
| granted | ||
| ? 'Notifications enabled' | ||
| : 'Permission request sent', |
Comment on lines
+200
to
+202
| : const Text( | ||
| 'Unlock for \$6.99', | ||
| style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), |
This was referenced May 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lands the v1.0 store-ready release of Invisible Habit Builder. Working tree contained the documented v1.0 implementation that was never committed; this PR is the catch-up.
Selector<OnboardingService, bool>first-launch routing inlib/main.dartadd_habit,analytics,onboarding,premium,settingsAdService(banner-only,--dart-definegated, premium-aware via_PremiumEntitlementBridge)OnboardingService(settings-box backed; no new HiveType)skip: truefor known flutter_test/Hive flush-timer hangs)USE_EXACT_ALARM+ minSdk 26google_mobile_ads ^5.3.1; description -> Invisible Habit Builder20 files changed, +3881 / -117.
Test plan
flutter pub get— greenflutter pub run build_runner build --delete-conflicting-outputs— 0 new outputs (codegen cached)flutter analyze --fatal-infos—No issues found!flutter test— 30 passed, 2 skipped (documented in CLAUDE.md)flutter build apk --debug—Built build/app/outputs/flutter-apk/app-debug.apk(180 MB)flutter build apk --release— deferred to store-submission day--dart-definekeys (RC_PUBLIC_API_KEY_{IOS,ANDROID},RC_LIFETIME_PRODUCT_ID,ADMOB_BANNER_ID_{ANDROID,IOS}) + Gradle-PadmobAppId— pre-launch checklistUSE_EXACT_ALARMjustification — pre-launch checklist🤖 Generated with Claude Code