import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../data/content_repository.dart'; import '../data/save_repository.dart'; import '../data/streak.dart'; import '../game/models/season.dart'; import '../services/ad_service.dart'; import '../services/analytics_service.dart'; import '../services/audio_service.dart'; import '../services/consent_service.dart'; import '../services/iap_service.dart'; import 'ads_notifier.dart'; import 'endless_best_notifier.dart'; import 'sound_notifier.dart'; import 'game_session_notifier.dart'; import 'progress_notifier.dart'; import 'season_flow_notifier.dart'; import 'streak_notifier.dart'; import 'tutorial_notifier.dart'; final gameSessionProvider = NotifierProvider( GameSessionNotifier.new, ); final soundEnabledProvider = NotifierProvider(SoundEnabledNotifier.new); final audioServiceProvider = Provider((ref) { final service = AudioService(enabled: ref.read(soundEnabledProvider)); ref.listen(soundEnabledProvider, (_, next) => service.enabled = next); ref.onDispose(service.dispose); return service; }); /// Overridden with the opened repository in main() (and in tests). final saveRepositoryProvider = Provider( (ref) => throw UnimplementedError('override with an opened SaveRepository'), ); final progressProvider = NotifierProvider>( ProgressNotifier.new, ); final seasonFlowProvider = NotifierProvider( SeasonFlowNotifier.new, ); final contentRepositoryProvider = Provider((ref) => ContentRepository()); final seasonsProvider = FutureProvider>((ref) { // Watching (not awaiting) the one-shot sync makes this provider re-run // once when the sync completes, picking up freshly cached packs. Local // content loads immediately; the network never blocks this future. ref.watch(seasonRefreshProvider); return ref.read(contentRepositoryProvider).availableSeasons(); }); /// One background content sync per app session. Home listens and refreshes /// the season list when new packs arrived. final seasonRefreshProvider = FutureProvider( (ref) => ref.read(contentRepositoryProvider).refresh(), ); /// The season players land in by default: the newest available. /// (availableSeasons is sorted by seasonId ascending.) SeasonPack activeSeason(List seasons) => seasons.last; final streakProvider = NotifierProvider( StreakNotifier.new, ); final tutorialProvider = NotifierProvider( TutorialNotifier.new, ); final endlessBestProvider = NotifierProvider( EndlessBestNotifier.new, ); final adsRemovedProvider = NotifierProvider(AdsRemovedNotifier.new); /// Reads ownership live from [adsRemovedProvider]; a mid-session purchase /// takes effect on the next ad decision without re-wiring. final adServiceProvider = Provider((ref) { final service = AdService(adsRemoved: () => ref.read(adsRemovedProvider)); ref.onDispose(service.dispose); return service; }); final consentServiceProvider = Provider( (ref) => ConsentService(ref.read(adServiceProvider)), ); /// A verified remove_ads purchase/restore grants the entitlement through the /// notifier (persists + flips state), which AdService and the banner observe. final iapServiceProvider = Provider((ref) { final service = IapService( onEntitlementGranted: () => ref.read(adsRemovedProvider.notifier).grant(), ); ref.onDispose(service.dispose); service.initialize(); return service; }); final analyticsProvider = Provider( (ref) => AnalyticsService(DebugAnalyticsBackend()), ); /// The visual theme of whatever season is in play; fallback outside seasons /// (home, endless). Pure model — UI converts via ThemeColors. final activeThemeProvider = Provider((ref) { final flow = ref.watch(seasonFlowProvider); return flow?.pack.theme ?? SeasonTheme.fallback; });