import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:app_links/app_links.dart'; import 'package:hive_flutter/hive_flutter.dart'; // google_mobile_ads removed — switched to Adsterra only import 'services/session_manager.dart'; import 'services/settings_service.dart'; import 'services/screen_time_service.dart'; import 'services/focusgram_router.dart'; import 'services/injection_controller.dart'; import 'services/credit_store.dart'; import 'services/bait_engine.dart'; import 'services/app_lock_service.dart'; import 'services/level_service.dart'; import 'services/snapshot_service.dart'; import 'screens/app_lock_screen.dart'; import 'screens/onboarding_page.dart'; import 'screens/main_webview_page.dart'; import 'screens/breath_gate_screen.dart'; import 'screens/app_session_picker.dart'; import 'screens/cooldown_gate_screen.dart'; import 'services/notification_service.dart'; import 'features/update_checker/update_checker_service.dart'; import 'features/preloader/instagram_preloader.dart'; import 'widgets/remote_popup_handler.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Lock to portrait await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); // ── Initialise storage & SDKs ────────────────────────────── await Hive.initFlutter(); final creditStore = CreditStore(); final baitEngine = BaitEngine(); final levelService = LevelService(); final appLockService = AppLockService(); final snapshotService = SnapshotService(); final sessionManager = SessionManager(); final settingsService = SettingsService(); final screenTimeService = ScreenTimeService(); final updateChecker = UpdateCheckerService(); await creditStore.init(); await baitEngine.init(); await appLockService.init(); await levelService.init(); await snapshotService.init(); await sessionManager.init(); await settingsService.init(); await screenTimeService.init(); await NotificationService().init(requestPermissions: true); runApp( MultiProvider( providers: [ ChangeNotifierProvider.value(value: sessionManager), ChangeNotifierProvider.value(value: settingsService), ChangeNotifierProvider.value(value: screenTimeService), ChangeNotifierProvider.value(value: creditStore), ChangeNotifierProvider.value(value: baitEngine), ChangeNotifierProvider.value(value: levelService), ChangeNotifierProvider.value(value: appLockService), ChangeNotifierProvider.value(value: snapshotService), ChangeNotifierProvider.value(value: updateChecker), ], child: const FocusGramApp(), ), ); // Fire and forget — preloads Instagram while app UI initialises. unawaited(InstagramPreloader.start(InjectionController.iOSUserAgent)); } class FocusGramApp extends StatelessWidget { const FocusGramApp({super.key}); @override Widget build(BuildContext context) { final settings = context.watch(); final isDark = settings.isDarkMode; return MaterialApp( title: 'FocusGram', debugShowCheckedModeBanner: false, theme: ThemeData( brightness: isDark ? Brightness.dark : Brightness.light, colorScheme: isDark ? ColorScheme.dark( primary: Colors.blue.shade400, surface: Colors.black, ) : ColorScheme.light(primary: Colors.blue), scaffoldBackgroundColor: isDark ? Colors.black : Colors.white, useMaterial3: true, splashColor: Colors.transparent, highlightColor: Colors.transparent, ), home: const InitialRouteHandler(), ); } } /// Flow on every cold open: /// 1. Onboarding (if first run) /// 2. Cooldown Gate (if app-open cooldown active) /// 3. Breath Gate (if enabled in settings) /// 4. If an app session is already active, resume it /// otherwise show App Session Picker /// 5. Main WebView class InitialRouteHandler extends StatefulWidget { const InitialRouteHandler({super.key}); @override State createState() => _InitialRouteHandlerState(); } class _InitialRouteHandlerState extends State with WidgetsBindingObserver { bool _breathCompleted = false; bool _appSessionStarted = false; bool _onboardingCompleted = false; bool _lockScreenDismissed = false; late AppLinks _appLinks; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _appLinks = AppLinks(); _initDeepLinks(); WidgetsBinding.instance.addPostFrameCallback((_) { RemotePopupHandler.checkAndShow(context); }); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { final appLock = context.read(); if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) { appLock.onBackgrounded(); } else if (state == AppLifecycleState.resumed) { if (appLock.shouldLockOnResume) { appLock.onLockScreenShown(); _showLockScreen(); } } } Future _showLockScreen() async { final result = await Navigator.push( context, MaterialPageRoute(builder: (_) => const AppLockScreen(forAppWide: true)), ); if (result == true && mounted) { setState(() => _lockScreenDismissed = true); } } Future _initDeepLinks() async { // 1. Handle background links while app is running _appLinks.uriLinkStream.listen((uri) { // debugPrint('Incoming Deep Link: $uri'); FocusGramRouter.pendingUrl.value = uri.toString(); }); // 2. Handle the initial link that opened the app final initialUri = await _appLinks.getInitialLink(); if (initialUri != null) { // debugPrint('Initial Deep Link: $initialUri'); FocusGramRouter.pendingUrl.value = initialUri.toString(); } } @override Widget build(BuildContext context) { final sm = context.watch(); final settings = context.watch(); final appLock = context.watch(); // Step 0: App-wide lock (shows before everything, once per cold start) if (appLock.needsUnlockOnStart && !_lockScreenDismissed) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!appLock.isShowingLock) { appLock.onLockScreenShown(); _showLockScreen(); } }); } // Step 1: Onboarding if (settings.isFirstRun && !_onboardingCompleted) { return OnboardingPage( onFinish: () => setState(() => _onboardingCompleted = true), ); } // Step 2: Cooldown gate — if too soon since last session if (sm.isAppOpenCooldownActive) { return const CooldownGateScreen(); } // Step 3: Breath gate if (settings.showBreathGate && !_breathCompleted) { return BreathGateScreen( durationSeconds: settings.breathGateSeconds, onFinish: () => setState(() => _breathCompleted = true), ); } // Step 4: App session picker / resume existing session if (!_appSessionStarted) { if (sm.isAppSessionActive) { // User already has an active app session — don't ask intention again. _appSessionStarted = true; } else { return AppSessionPickerScreen( onSessionStarted: () => setState(() => _appSessionStarted = true), ); } } // Step 5: Main app return const MainWebViewPage(); } }