diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml index 52d1073e..a801c54b 100644 --- a/android/app/src/main/res/values-night-v31/styles.xml +++ b/android/app/src/main/res/values-night-v31/styles.xml @@ -15,6 +15,6 @@ This Theme is only used starting with V2 of Flutter's Android embedding. --> diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index dbc9ea9f..a3496baf 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -17,6 +17,6 @@ This Theme is only used starting with V2 of Flutter's Android embedding. --> diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml index a6497f85..c71b5755 100644 --- a/android/app/src/main/res/values-v31/styles.xml +++ b/android/app/src/main/res/values-v31/styles.xml @@ -15,6 +15,6 @@ This Theme is only used starting with V2 of Flutter's Android embedding. --> diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 0d1fa8fc..ec261ecd 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -17,6 +17,6 @@ This Theme is only used starting with V2 of Flutter's Android embedding. --> diff --git a/lib/main.dart b/lib/main.dart index ad8f8130..51477a6a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,37 +13,52 @@ import 'package:spark/src/core/utils/logging/riverpod_logger.dart'; import 'package:spark/src/sprk_app.dart'; void main() async { - WidgetsFlutterBinding.ensureInitialized(); + final binding = WidgetsFlutterBinding.ensureInitialized(); + binding.deferFirstFrame(); + var didAllowFirstFrame = false; - await dotenv.load(); - SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitUp, - DeviceOrientation.portraitDown, - ]); + void allowFirstFrame() { + if (didAllowFirstFrame) { + return; + } + didAllowFirstFrame = true; + binding.allowFirstFrame(); + } - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); - SystemChrome.setSystemUIOverlayStyle(AppTheme.darkSystemUiStyle); + try { + await dotenv.load(); + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); - fvp.registerWith(); + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + SystemChrome.setSystemUIOverlayStyle(AppTheme.darkSystemUiStyle); - await initServiceLocator(); + fvp.registerWith(); - // Setup logging for production/debug - _setupLogging(); + await initServiceLocator(); - // Initialize auth repository - await _initializeAuth(); + // Setup logging for production/debug + _setupLogging(); - // Create a ProviderContainer with the Riverpod logger - final container = riverpod.ProviderContainer( - observers: [SparkRiverpodLogger()], - ); - runApp( - riverpod.UncontrolledProviderScope( - container: container, - child: const SprkApp(), - ), - ); + // Initialize auth repository + await _initializeAuth(); + + // Create a ProviderContainer with the Riverpod logger + final container = riverpod.ProviderContainer( + observers: [SparkRiverpodLogger()], + ); + runApp( + riverpod.UncontrolledProviderScope( + container: container, + child: SprkApp(onFirstRouteReady: allowFirstFrame), + ), + ); + } catch (_) { + allowFirstFrame(); + rethrow; + } } /// Setup logging framework based on environment diff --git a/lib/src/sprk_app.dart b/lib/src/sprk_app.dart index 2108cd13..0f3cc2d4 100644 --- a/lib/src/sprk_app.dart +++ b/lib/src/sprk_app.dart @@ -13,7 +13,9 @@ import 'package:spark/src/features/feed/providers/feed_provider.dart'; import 'package:spark/src/features/settings/providers/settings_provider.dart'; class SprkApp extends ConsumerStatefulWidget { - const SprkApp({super.key}); + const SprkApp({required this.onFirstRouteReady, super.key}); + + final VoidCallback onFirstRouteReady; @override ConsumerState createState() => _SprkAppState(); @@ -22,10 +24,12 @@ class SprkApp extends ConsumerStatefulWidget { class _SprkAppState extends ConsumerState { final _appRouter = AppRouter(); final SparkLogger _logger = GetIt.instance().getLogger('SprkApp'); + bool _didReleaseFirstFrame = false; @override void initState() { super.initState(); + _appRouter.addListener(_releaseFirstFrameWhenRouteIsReady); // Register AppRouter globally for push notification navigation if (!GetIt.instance.isRegistered()) { GetIt.instance.registerSingleton(_appRouter); @@ -39,6 +43,24 @@ class _SprkAppState extends ConsumerState { }); } + @override + void dispose() { + _appRouter.removeListener(_releaseFirstFrameWhenRouteIsReady); + super.dispose(); + } + + void _releaseFirstFrameWhenRouteIsReady() { + if (_didReleaseFirstFrame || !_appRouter.hasEntries) { + return; + } + + _didReleaseFirstFrame = true; + _appRouter.removeListener(_releaseFirstFrameWhenRouteIsReady); + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.onFirstRouteReady(); + }); + } + Future _initializeApp() async { try { _logger.d('Syncing user preferences from server...'); @@ -89,6 +111,7 @@ class _SprkAppState extends ConsumerState { return supportedLocales.first; }, routerConfig: _appRouter.config( + placeholder: (_) => const ColoredBox(color: Colors.black), navigatorObservers: () => [ PosthogObserver(nameExtractor: _postHogScreenName), ],