Claude/setup mobile dev environment 0 t sg b - #1
Open
iztokio wants to merge 23 commits into
Open
Conversation
Stage 0 complete. Full project foundation for Horoscope+Tarot app: ## Documentation (docs/stage0/) - PRD-Lite: product requirements, user stories, acceptance criteria - ROADMAP: 7-stage development plan with tasks and gates - RISK-LIST: 25+ risks across Legal/Tech/Product/Security with mitigations - THREAT-MODEL: STRIDE analysis, Firestore security rules, data classification - TECH-STACK: Flutter 3.27.4 + Firebase + Riverpod + GoRouter + Drift - SETUP-GUIDE: Full developer onboarding instructions ## Flutter App (app/) - pubspec.yaml: 30+ production dependencies (Firebase, Billing, AdMob, etc.) - Dart 3.4 / Flutter 3.22+ compatible, all packages resolved - Clean Architecture (Feature-First) directory structure - Core domain entities: UserProfile, TarotCard, DailyReading, SubscriptionStatus - ZodiacCalculator: deterministic zodiac sign from birth date - DailyReading: deterministic seed engine (same card every day) - SubscriptionStatus: all billing states with fail-safe security logic - AppTheme: full Material3 design system (dark cosmic + light dawn) - AppConstants: all constants, Firestore paths, analytics events - AppError / Result<T>: Railway-oriented error handling - 45 unit tests passing (zodiac, seed, subscription security) - analysis_options.yaml: very_good_analysis rules ## Android - AndroidManifest.xml: permissions, FCM, deep links, AdMob - build.gradle: Firebase BoM, R8 minification, ProGuard rules - settings.gradle: Firebase plugins - MainActivity.kt ## Backend (Firebase Functions) - purchase-verification.ts: server-side Google Play API verification - handlePlayRTDN: real-time subscription state updates (Pub/Sub) - restorePurchases: restore flow - SECURITY: client can never set subscriptionStatus directly ## CI (GitHub Actions) - analyze: dart format + flutter analyze - test: unit tests with coverage threshold - build-android: debug APK - security: gitleaks + trufflehog secrets scan - build-release: AAB on main branch (with signing) DISCLAIMER: App is for entertainment purposes only. No medical/financial/legal advice implied. https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
## Stage 0 PATCH (Gate Audit fixes) - Brand identity: name AstraVia selected (Astra=stars + Via=path) - BRAND-IDENTITY.md: competitor analysis, naming, colors, typography, ASO - ASSET-LICENSES.md: legal documentation framework for all assets - .env.example: environment variables template - l10n.yaml: Flutter localization generation config ## Stage 1 — UX/Product Design - USER-FLOWS.md: 3 personas, full IA, 5 user flows, screen specs, wireframes - Flow: Install → Horoscope in ≤90 seconds - Flow: Daily return → Content in ≤1 second (cache) - Flow: Premium unlock with server verification - Accessibility requirements (WCAG AA, 44dp tap targets) ## Localization (4 languages, 100% coverage) - app_en.arb: 80+ keys (EN — complete) - app_ru.arb: 80+ keys (RU — complete) - app_es.arb: 80+ keys (ES — complete) - app_pt.arb: 80+ keys (PT — complete) ## Flutter Screens (production-ready UI) - OnboardingWelcomeScreen: animated star hero, CTA, disclaimer footer - OnboardingDisclaimerScreen: scroll-to-unlock CTA, PopScope protection, exit dialog, progress dots — COMPLIANCE CRITICAL - PaywallScreen: benefits first, annual/monthly toggle, trial CTA, restore purchases, legal footer, loading state - TodayScreen: horoscope card with category pills (General/Love/Work/Wellbeing), deterministic Tarot card reveal animation, 3-card premium gate, offline indicator, disclaimer badge ## Design System (app_theme.dart) - Full Material3 dark theme: #0D0B2A background, #D4AF37 gold accent - AppGradients: cosmicBackground, goldShimmer, cardOverlay - AppSpacing + AppRadius constants - Typography: Cinzel (headlines) + Raleway (body) 45/45 unit tests passing | 0 analyzer errors | 0 warnings https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
Marketing research complete: AstraLume selected over AstraVia. AstraLume = Astra (stars) + Lume (light) — mystical score 9/10, lower TM risk, natural in all 4 languages (EN/ES/PT/RU). Changes: - BRAND-IDENTITY.md v1.1.0: full AstraLume brand, bundle ID, lavender accent - app_constants.dart: appName='AstraLume', appId='com.astralume.horoscope' - build.gradle: namespace + applicationId → com.astralume.horoscope - AndroidManifest.xml: app label + deep link host → astralume.app - main.dart: class AstraLumeApp, title 'AstraLume', splash '✦ ASTRALUME ✦' - onboarding_disclaimer_screen.dart: AstraVia → AstraLume - ASSET-LICENSES.md, USER-FLOWS.md: AstraVia → AstraLume Tests: 45/45 pass https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
Stage 2: Full offline-first data architecture.
## Database (Drift / SQLite — schema v1)
- app_database.dart: 4 tables with WAL mode + foreign keys
- user_profiles, daily_readings, tarot_readings, subscription_cache
- build.yaml: mockito builder disabled (incompatible with analyzer 7.6.0)
- build_runner runs in CI before tests (generates .g.dart)
## Security Rules
- firestore.rules: production-ready Firestore Security Rules
- Client CANNOT write subscriptionStatus (Cloud Functions only)
- Disclaimer records are immutable (create-only, audit trail)
- Content collection is read-only for authenticated users
## Repositories (offline-first, Clean Architecture)
- UserProfileRepository (interface + impl): local profile CRUD
- HoroscopeRepository (interface + impl): cache-first daily readings
- SubscriptionRepository (interface + impl): fail-safe premium gating
- TarotRepository (interface): contract for Stage 3 impl
- Deterministic DB key: {uid}_{YYYYMMDD}_{sign} per zodiac row
## Riverpod Providers
- core_providers.dart: database, repositories, state providers
- hasPremiumAccessProvider: fail-safe (false when unknown state)
- todayReadingProvider, subscriptionStatusProvider (StreamProvider)
## Tests: 29 new (74 total)
- app_database_test.dart: 7 schema tests (in-memory Drift)
- horoscope_repository_test.dart: 9 offline-first tests
- subscription_repository_test.dart: 13 security-critical tests
(all subscription states, stream reactivity, fail-safe)
## Docs
- docs/stage2/ARCHITECTURE.md: full ADR + provider hierarchy
- docs/stage2/DATA-CONTRACTS.md: Firestore schema + Cloud Functions API
pubspec.yaml: mockito/fake_cloud_firestore deferred to Stage 3
https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
…eens - firebase_options.dart: placeholder with offline fallback (UnsupportedError) - auth_service.dart: anonymous Firebase Auth + Riverpod providers (authServiceProvider, authStateProvider, authUserIdProvider) - notification_service.dart: FCM foreground/background handler + local notification channel setup - settings_screen.dart: full settings UI (language, notifications, theme, premium badge, disclaimer, delete account) - history_screen.dart: reading history list (7-day free / 90-day premium) with ExpansionTile per entry - main.dart: Firebase.initializeApp with try/catch offline fallback, MaterialApp.router via appRouterProvider - docs/stage3/FIREBASE-SETUP.md: step-by-step Firebase project setup guide + security checklist App runs in offline mode when Firebase is not configured. https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
…asources Onboarding screens: - onboarding_welcome_screen.dart: ASTRALUME branding + GoRouter CTA - onboarding_birthdate_screen.dart: date picker, zodiac reveal, COPPA validation, saves UserProfile to Drift - onboarding_notifications_screen.dart: FCM opt-in, completeOnboarding() → router redirect Routing (app_router.dart): - Auth-aware GoRouter redirect: loading→splash, not onboarded→/onboarding, done→/home/today - Real SplashScreen fires signInAnonymously() in initState - MainShell with NavigationBar (Today/History/Settings tabs) - All stub screens replaced with real screen imports Firestore datasources: - horoscope_remote_datasource.dart: fetch/history/markViewed, offline-safe - subscription_remote_datasource.dart: watchStatus stream, fetchStatus, submitPurchaseForValidation Repository wiring: - HoroscopeRepositoryImpl: Firestore fetch → local cache fallback - SubscriptionRepositoryImpl: Firestore stream → Drift cache sync (_startRemoteSync) - core_providers.dart: all new providers wired (remote datasources + notification service) Tests (+28 new, 74 total, all passing): - user_profile_test.dart: fromOnboarding, validateBirthDate (COPPA), copyWith, ZodiacCalculator boundaries (17 tests) - onboarding_flow_test.dart: full repository round-trip, disclaimer, completeOnboarding, deleteAllData (7 tests) - horoscope_remote_datasource_test.dart: docId format, zero-padding, uniqueness (4 tests) flutter analyze: 0 errors (64 info/warning) https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
- auth_service.dart: FirebaseAuth? nullable, all methods null-safe, firebaseAuthProvider returns FirebaseAuth? with try/catch - notification_service.dart: FirebaseMessaging? nullable, static FCM calls wrapped in try/catch, firebaseMessagingProvider → T? - main.dart: FirebaseCrashlytics.instance.recordError wrapped in try/catch (was crashing runZonedGuarded on every uncaught error) - horoscope_remote_datasource.dart + subscription_remote_datasource.dart: providers return T? with try/catch (already done, included in diff) - app_database.dart: _openConnection falls back to getApplicationSupportDirectory() then /tmp for headless/CI envs - app_router.dart: remove !onSplash guard — splash is a transit state that must always redirect forward when loading completes - onboarding_birthdate_screen.dart: remove premature ref.invalidate(userProfileProvider) that caused loading→splash loop - onboarding_disclaimer_screen.dart: _onAccept now pushes to /onboarding/notifications instead of Navigator.pop() - onboarding_notifications_screen.dart: _completeOnboarding uses authUserIdProvider (always non-null) instead of currentUserIdProvider (which was null until userProfileProvider reloaded) Tested via Linux desktop (Xvfb): full flow Welcome→Birthdate→ DatePicker→ZodiacReveal→Disclaimer→Notifications→Today works in offline mode without any Firebase errors. https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
Generated via `flutter create --platforms=linux .` for visual testing on headless environments (Xvfb). Includes GTK runner, CMakeLists, plugin registrant stubs, and app-level .gitignore / .metadata. https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
- app_database_test: upsertDailyReading used hardcoded date DateTime(2026,2,16)+24h = Feb 17 midnight, which is now in the past. Fixed to use DateTime.now() + computed id dynamically to match AppDatabase._dateToStr() format. - widget_test.dart: default Flutter boilerplate referenced nonexistent MyApp constructor. Replaced with empty placeholder (real widget tests live in test/widget/). https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
- locale_provider.dart: SharedPreferences-backed LocaleNotifier with 4 supported languages (ru/en/es/pt), Russian as default - pubspec.yaml: register assets/images/backgrounds/ directory - Created assets/images/backgrounds/ folder for user-provided images https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
Directory for user-provided background images (bg_main, bg_tarot). https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
- Watch todayReadingProvider, currentZodiacSignProvider, appLanguageProvider instead of hardcoded values - _HiddenCard: show tarot_back.png via Image.asset (✦ as graceful fallback) - _RevealedCard: show tarot_major_N.png based on card number; falls back to tarot_back.png for minor arcana (images pending); adds reversed position styling (warning color vs success color) - _RevealedCard: resolve canonical Major Arcana names (The Fool … The World) when entity contains placeholder 'Card N' names - _HoroscopeCard: show real horoscope text per category from DailyReading (general/love/work/wellbeing); skeleton shimmer while loading - _TodayAppBar: bell and profile buttons now navigate to Settings tab - _PremiumGateSheet: Unlock Premium button navigates to paywall route - _onTap3CardSpread: skips paywall sheet if user already has premium https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
TarotLocalDatasource: - Full 78-card deck bundled as constants (no network required) - Major Arcana (0-21): proper names in EN/ES/PT/RU + full multilingual meanings for upright, reversed, love, work, health categories - Minor Arcana (22-77): algorithmically generated from suit+rank templates covering all 4 suits × 14 ranks; asset path set for major arcana images - Singleton with in-memory access — zero IO cost on getByIndex/search TarotRemoteDatasource: - Firestore reader for content/tarot/cards collection - Nullable provider pattern (matches HoroscopeRemoteDatasource) — returns null when Firebase is not configured; repository falls back to local data - Full Firestore → TarotCard mapping with graceful error handling TarotRepositoryImpl: - Offline-first: local bundled data always available, Firestore enriches in-memory via _remoteOverrides map (no extra DB table for metadata) - getDailyCard: deterministic via DailyReading.generateSeed; cached in TarotReadingsTable with 24h TTL - getThreeCardSpread: separate deterministic seed (XOR offset); cached same way; premium guard enforced at repository layer - prefetchCardLibrary: fetches all 78 Firestore docs and merges into _remoteOverrides for richer content when network available core_providers.dart: - tarotRepositoryProvider wired with DB + nullable remote - dailyCardProvider: FutureProvider for today's card - threeCardSpreadProvider: FutureProvider for premium 3-card spread https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
…pread - Full 3-card spread UI with flip animation (Matrix4 scaleX two-phase) - _SpreadCardSlot: AnimationController-driven card flip; glowing shadow on selected card; card name fades in after reveal - _CardFace: shows tarot_major_N.png for Major Arcana, tarot_back.png as fallback for Minor Arcana (images pending) - _CardDetailPanel: animated panel shows position label (Past/Present/ Future), upright/reversed badge, card name, and full meaning text; AnimatedSwitcher for smooth card-to-card transitions - _AppBar: custom back button, centered "3-CARD SPREAD" title in Cinzel - Loading skeleton, error state with retry, premium gate safety net - app_router.dart: replace Stage 4 placeholder class with real import; remove dead TarotDrawScreen stub https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
…elete account Language update (_showLanguagePicker): - Updates profile via UserProfileRepository.updateProfile() - Invalidates userProfileProvider + todayReadingProvider (content re-fetches in the new language on next build) - Shows confirmation snackbar with selected language name Notification reschedule (_showTimePicker): - Saves new time to profile via updateProfile() - Calls NotificationService.scheduleDailyNotification() with new HH:mm - Shows confirmation snackbar with the selected time - No-op if same time as current setting Delete account (_confirmDeleteAccount): - Cancels scheduled daily notification (clean slate) - Deletes all local Drift data via UserProfileRepository.deleteAllData() - Deletes Firebase Auth anonymous account via AuthService.deleteAccount() - Invalidates userProfileProvider + todayReadingProvider; GoRouter redirect detects missing profile and navigates user back to onboarding - Shows error snackbar on failure (instead of silent fail) Added imports: auth_service.dart, notification_service.dart https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
- app/web/: Flutter web platform files (index.html, manifest.json, etc.) added via `flutter create . --platforms=web` for web testing capability - app/.metadata: updated active_platforms to include web https://claude.ai/code/session_01JtQH6QihZBptx8ZZHY7wCu
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.
No description provided.