import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/providers/settings.provider.dart'; import 'package:twonly/src/services/api/media_upload.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/onboarding/onboarding.view.dart'; import 'package:twonly/src/views/home.view.dart'; import 'package:twonly/src/views/onboarding/register.view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'dart:async'; import 'package:url_launcher/url_launcher.dart'; // these two callbacks are called on updated to the corresponding database /// The Widget that configures your application. class App extends StatefulWidget { const App({super.key}); @override State createState() => _AppState(); } class _AppState extends State with WidgetsBindingObserver { bool wasPaused = false; bool appIsOutdated = false; @override void initState() { super.initState(); globalIsAppInBackground = false; WidgetsBinding.instance.addObserver(this); // register global callbacks to the widget tree globalCallbackConnectionState = (update) { context.read().updateConnectionState(update); setUserPlan(); }; initAsync(); } Future setUserPlan() async { final user = await getUser(); globalBestFriendUserId = -1; if (user != null && mounted) { if (user.myBestFriendContactId != null) { final contact = await twonlyDB.contactsDao .getContactByUserId(user.myBestFriendContactId!) .getSingleOrNull(); if (contact != null) { if (contact.alsoBestFriend) { globalBestFriendUserId = user.myBestFriendContactId ?? 0; } } } if (mounted) { context.read().updatePlan(user.subscriptionPlan); } } } Future initAsync() async { setUserPlan(); globalCallbackAppIsOutdated = () async { context.read().updateConnectionState(false); setState(() { appIsOutdated = true; }); }; await apiService.connect(force: true); apiService.listenToNetworkChanges(); // call this function so invalid media files are get purged retryMediaUpload(true); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { if (wasPaused) { globalIsAppInBackground = false; twonlyDB.markUpdated(); apiService.connect(force: true); } } else if (state == AppLifecycleState.paused) { wasPaused = true; globalIsAppInBackground = true; } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); globalCallbackConnectionState = (a) {}; globalCallbackAppIsOutdated = () {}; super.dispose(); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: context.watch(), builder: (BuildContext context, Widget? child) { return MaterialApp( restorationScopeId: 'app', localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [ Locale('en', ''), Locale('de', ''), ], onGenerateTitle: (BuildContext context) => "twonly", theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF57CC99), ), pageTransitionsTheme: const PageTransitionsTheme( builders: { TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), }, ), inputDecorationTheme: const InputDecorationTheme( border: OutlineInputBorder(), ), ), darkTheme: ThemeData.dark().copyWith( colorScheme: ColorScheme.fromSeed( brightness: Brightness.dark, seedColor: const Color(0xFF57CC99), ), inputDecorationTheme: const InputDecorationTheme( border: OutlineInputBorder(), ), ), themeMode: context.watch().themeMode, initialRoute: '/', routes: { "/": (context) => AppMainWidget(initialPage: 1, appIsOutdated: appIsOutdated), "/chats": (context) => AppMainWidget(initialPage: 0, appIsOutdated: appIsOutdated) }, ); }, ); } } class AppMainWidget extends StatefulWidget { const AppMainWidget( {super.key, required this.initialPage, required this.appIsOutdated}); final int initialPage; final bool appIsOutdated; @override State createState() => _AppMainWidgetState(); } class _AppMainWidgetState extends State { Future userCreated = isUserCreated(); bool showOnboarding = kReleaseMode; @override Widget build(BuildContext context) { return Stack( children: [ FutureBuilder( future: userCreated, builder: (context, snapshot) { if (!snapshot.hasData) { return Center(child: Container()); } if (snapshot.data!) { return HomeView( initialPage: widget.initialPage, ); } return showOnboarding ? OnboardingView( callbackOnSuccess: () { setState(() { showOnboarding = false; }); }, ) : RegisterView( callbackOnSuccess: () { setState(() { userCreated = isUserCreated(); }); }, ); }, ), if (widget.appIsOutdated) Positioned( top: 60, left: 30, right: 30, child: SafeArea( child: Container( padding: EdgeInsets.symmetric(vertical: 10, horizontal: 8), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(10), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( context.lang.appOutdated, textAlign: TextAlign.center, softWrap: true, style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(color: Colors.white, fontSize: 16), ), if (Platform.isAndroid) SizedBox(height: 5), if (Platform.isAndroid) ElevatedButton( onPressed: () { launchUrl(Uri.parse( "https://play.google.com/store/apps/details?id=eu.twonly")); }, style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: Text( context.lang.appOutdatedBtn, style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(color: Colors.white, fontSize: 16), ), ), ], ), ), ), ), ], ); } }