import 'dart:developer'; import 'dart:isolate'; import 'dart:ui'; import 'package:background_downloader/background_downloader.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:intl/intl.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:OpenContacts/apis/github_api.dart'; import 'package:OpenContacts/client_holder.dart'; import 'package:OpenContacts/clients/api_client.dart'; import 'package:OpenContacts/clients/inventory_client.dart'; import 'package:OpenContacts/clients/messaging_client.dart'; import 'package:OpenContacts/clients/session_client.dart'; import 'package:OpenContacts/clients/settings_client.dart'; import 'package:OpenContacts/models/sem_ver.dart'; import 'package:OpenContacts/widgets/homepage.dart'; import 'package:OpenContacts/widgets/login_screen.dart'; import 'package:OpenContacts/widgets/update_notifier.dart'; import 'models/authentication_data.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( systemStatusBarContrastEnforced: true, systemNavigationBarColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent, ), ); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: [SystemUiOverlay.top]); await Hive.initFlutter(); final dateFormat = DateFormat.Hms(); Logger.root.onRecord.listen( (event) => log("${dateFormat.format(event.time)}: ${event.message}", name: event.loggerName, time: event.time)); final settingsClient = SettingsClient(); await settingsClient.loadSettings(); final newSettings = settingsClient.currentSettings.copyWith(machineId: settingsClient.currentSettings.machineId.valueOrDefault); await settingsClient.changeSettings(newSettings); // Save generated machineId to disk AuthenticationData cachedAuth = AuthenticationData.unauthenticated(); try { cachedAuth = await ApiClient.tryCachedLogin(); } catch (_) { // Ignore } runApp(recon(settingsClient: settingsClient, cachedAuthentication: cachedAuth)); } class recon extends StatefulWidget { const recon({required this.settingsClient, required this.cachedAuthentication, super.key}); final SettingsClient settingsClient; final AuthenticationData cachedAuthentication; @override State createState() => _reconState(); } class _reconState extends State { final Typography _typography = Typography.material2021(platform: defaultTargetPlatform); final ReceivePort _port = ReceivePort(); late AuthenticationData _authData = widget.cachedAuthentication; bool _checkedForUpdate = false; void showUpdateDialogOnFirstBuild(BuildContext context) { final navigator = Navigator.of(context); final settings = ClientHolder.of(context).settingsClient; if (_checkedForUpdate) return; _checkedForUpdate = true; GithubApi.getLatestTagName().then((remoteVer) async { final currentVer = (await PackageInfo.fromPlatform()).version; SemVer currentSem; SemVer remoteSem; SemVer lastDismissedSem; try { currentSem = SemVer.fromString(currentVer); } catch (_) { currentSem = SemVer.zero(); } try { lastDismissedSem = SemVer.fromString(settings.currentSettings.lastDismissedVersion.valueOrDefault); } catch (_) { lastDismissedSem = SemVer.zero(); } try { remoteSem = SemVer.fromString(remoteVer); } catch (_) { return; } if (remoteSem <= lastDismissedSem && lastDismissedSem.isNotZero) { return; } if (remoteSem > currentSem && navigator.overlay?.context != null && context.mounted) { showDialog( context: navigator.overlay!.context, builder: (context) { return UpdateNotifier( remoteVersion: remoteSem, localVersion: currentSem, ); }, ); } }); } @override void initState() { super.initState(); IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port'); _port.listen((dynamic data) { // Not useful yet? idk... // String id = data[0]; // DownloadTaskStatus status = data[1]; // int progress = data[2]; }); FileDownloader().updates.listen(downloadCallback); } @override void dispose() { IsolateNameServer.removePortNameMapping('downloader_send_port'); super.dispose(); } @pragma('vm:entry-point') static void downloadCallback(TaskUpdate event) {} @override Widget build(BuildContext context) { return Phoenix( child: Builder(builder: (context) { return ClientHolder( settingsClient: widget.settingsClient, authenticationData: _authData, onLogout: () { setState(() { _authData = AuthenticationData.unauthenticated(); }); Phoenix.rebirth(context); }, child: DynamicColorBuilder( builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) => MaterialApp( debugShowCheckedModeBanner: true, title: 'OpenContacts', theme: ThemeData( useMaterial3: true, textTheme: _typography.black, colorScheme: lightDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.light), ), darkTheme: ThemeData( useMaterial3: true, textTheme: _typography.white, colorScheme: darkDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark), ), themeMode: ThemeMode.values[widget.settingsClient.currentSettings.themeMode.valueOrDefault], home: Builder( // Builder is necessary here since we need a context which has access to the ClientHolder builder: (context) { showUpdateDialogOnFirstBuild(context); final clientHolder = ClientHolder.of(context); return _authData.isAuthenticated ? MultiProvider( providers: [ ChangeNotifierProvider( create: (context) => MessagingClient( apiClient: clientHolder.apiClient, settingsClient: clientHolder.settingsClient, notificationClient: clientHolder.notificationClient, ), ), ChangeNotifierProvider( create: (context) => SessionClient( apiClient: clientHolder.apiClient, settingsClient: clientHolder.settingsClient, ), ), ChangeNotifierProvider( create: (context) => InventoryClient( apiClient: clientHolder.apiClient, ), ) ], child: AnnotatedRegion( value: SystemUiOverlayStyle( statusBarColor: Theme.of(context).colorScheme.surfaceVariant, ), child: const Home(), ), ) : LoginScreen( onLoginSuccessful: (AuthenticationData authData) async { if (authData.isAuthenticated) { setState(() { _authData = authData; }); } }, ); }, ), ), ), ); }), ); } }