import 'dart:async'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/providers/routing.provider.dart'; import 'package:twonly/src/providers/settings.provider.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/pow.dart'; import 'package:twonly/src/visual/components/app_outdated.comp.dart'; import 'package:twonly/src/visual/themes/dark.dart'; import 'package:twonly/src/visual/themes/light.dart'; import 'package:twonly/src/visual/views/critical_error.view.dart'; import 'package:twonly/src/visual/views/home.view.dart'; import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart'; import 'package:twonly/src/visual/views/onboarding/register.view.dart'; import 'package:twonly/src/visual/views/onboarding/setup.view.dart'; import 'package:twonly/src/visual/views/recovery.view.dart'; import 'package:twonly/src/visual/views/unlock_twonly.view.dart'; class App extends StatefulWidget { const App({ required this.storageError, required this.recoveryPossible, super.key, }); final bool storageError; final bool recoveryPossible; @override State createState() => _AppState(); } class _AppState extends State with WidgetsBindingObserver { bool _wasPaused = false; @override void initState() { super.initState(); AppState.isAppInBackground = false; WidgetsBinding.instance.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { if (_wasPaused) { AppState.isAppInBackground = false; twonlyDB.markUpdated(); unawaited(apiService.connect()); } } else if (state == AppLifecycleState.paused) { _wasPaused = true; AppState.isAppInBackground = true; } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: context.read(), builder: (context, child) { const localizationsDelegates = [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ]; const supportedLocales = [ Locale('en', ''), Locale('de', ''), ]; if (widget.storageError) { return MaterialApp( localizationsDelegates: localizationsDelegates, debugShowCheckedModeBanner: false, supportedLocales: supportedLocales, title: 'twonly', theme: lightTheme, darkTheme: darkTheme, themeMode: context.read().themeMode, home: const CriticalErrorView(), ); } if (widget.recoveryPossible) { return MaterialApp( localizationsDelegates: localizationsDelegates, debugShowCheckedModeBanner: false, supportedLocales: supportedLocales, title: 'twonly', theme: lightTheme, darkTheme: darkTheme, themeMode: context.read().themeMode, home: const RecoveryView(), ); } return MaterialApp.router( routerConfig: routerProvider, localizationsDelegates: localizationsDelegates, debugShowCheckedModeBanner: false, supportedLocales: supportedLocales, title: 'twonly', theme: lightTheme, darkTheme: darkTheme, themeMode: context.read().themeMode, ); }, ); } } class AppMainWidget extends StatefulWidget { const AppMainWidget({ required this.initialPage, super.key, }); final int initialPage; @override State createState() => _AppMainWidgetState(); } class _AppMainWidgetState extends State { bool _showOnboarding = true; bool _isLoaded = false; bool _isTwonlyLocked = true; bool _wasLogged = true; (Future?, bool) _proofOfWork = (null, false); @override void initState() { super.initState(); Log.info('AppWidgetState: initState started'); initAsync(); } Future initAsync() async { Log.info('AppWidgetState: initAsync started'); if (userService.isUserCreated) { try { unawaited(FirebaseMessaging.instance.requestPermission()); } catch (e) { Log.error(e); } if (_isTwonlyLocked) { // do not change in case twonly was already unlocked at some point _isTwonlyLocked = userService.currentUser.screenLockEnabled; } } else { // This means the user is in the onboarding screen, so start with the Proof of Work. final (proof, disabled) = await apiService.getProofOfWork(); if (proof != null) { Log.info('Starting with proof of work calculation.'); _proofOfWork = ( calculatePoW(proof.prefix, proof.difficulty.toInt()), false, ); } else { _proofOfWork = (null, disabled); } } setState(() { _isLoaded = true; }); } @override Widget build(BuildContext context) { if (!_wasLogged) { Log.info('AppWidgetState: build started (_isLoaded: $_isLoaded)'); if (_isLoaded) { _wasLogged = true; } } if (!_isLoaded) { return Center(child: Container()); } late Widget child; if (userService.isUserCreated) { if (_isTwonlyLocked) { child = UnlockTwonlyView( callbackOnSuccess: () => setState(() { _isTwonlyLocked = false; }), ); } else if (!userService.currentUser.skipSetupPages && userService.currentUser.currentSetupPage != null) { // This will only be shown in case the user have not skipped child = SetupView( onUpdate: () => setState(() { // userService.currentUser has updated... }), ); } else { child = HomeView( initialPage: widget.initialPage, ); } } else if (_showOnboarding) { child = OnboardingView( callbackOnSuccess: () => setState(() { _showOnboarding = false; }), ); } else { child = RegisterView( callbackOnSuccess: initAsync, proofOfWork: _proofOfWork, ); } return Stack( children: [ child, const AppOutdatedComp(), ], ); } }