Skip to content

feat(v1.0): land store-ready release#12

Open
Outtsett wants to merge 1 commit intomainfrom
release/v1.0
Open

feat(v1.0): land store-ready release#12
Outtsett wants to merge 1 commit intomainfrom
release/v1.0

Conversation

@Outtsett
Copy link
Copy Markdown
Owner

@Outtsett Outtsett commented May 4, 2026

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.

  • Onboarding flow + Selector<OnboardingService, bool> first-launch routing in lib/main.dart
  • 5 new screens: add_habit, analytics, onboarding, premium, settings
  • AdService (banner-only, --dart-define gated, premium-aware via _PremiumEntitlementBridge)
  • OnboardingService (settings-box backed; no new HiveType)
  • 5 new test files (30 pass, 2 documented skip: true for known flutter_test/Hive flush-timer hangs)
  • 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

20 files changed, +3881 / -117.

Test plan

  • flutter pub get — green
  • flutter pub run build_runner build --delete-conflicting-outputs — 0 new outputs (codegen cached)
  • flutter analyze --fatal-infosNo issues found!
  • flutter test — 30 passed, 2 skipped (documented in CLAUDE.md)
  • flutter build apk --debugBuilt build/app/outputs/flutter-apk/app-debug.apk (180 MB)
  • flutter build apk --release — deferred to store-submission day
  • iOS release build / TestFlight — deferred (requires Mac)
  • Wire --dart-define keys (RC_PUBLIC_API_KEY_{IOS,ANDROID}, RC_LIFETIME_PRODUCT_ID, ADMOB_BANNER_ID_{ANDROID,IOS}) + Gradle -PadmobAppId — pre-launch checklist
  • Tick iOS "Time Sensitive Notifications" capability in Xcode — pre-launch checklist
  • Draft Play Store USE_EXACT_ALARM justification — pre-launch checklist

🤖 Generated with Claude Code

- 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>
Copilot AI review requested due to automatic review settings May 4, 2026 17:46
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
);
Comment thread ios/Runner/Info.plist
<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>
Comment thread pubspec.yaml
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),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants