LCOV - code coverage report
Current view: top level - lib - main.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 166 0.0 %
Date: 2024-10-10 18:23:49 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:convert';
       3             : import 'package:cwtch/config.dart';
       4             : import 'package:cwtch/notification_manager.dart';
       5             : import 'package:cwtch/views/doublecolview.dart';
       6             : import 'package:cwtch/views/messageview.dart';
       7             : import 'package:flutter/foundation.dart';
       8             : import 'package:cwtch/cwtch/ffi.dart';
       9             : import 'package:cwtch/cwtch/gomobile.dart';
      10             : import 'package:flutter/material.dart';
      11             : import 'package:cwtch/errorHandler.dart';
      12             : import 'package:cwtch/settings.dart';
      13             : import 'package:cwtch/torstatus.dart';
      14             : import 'package:flutter/services.dart';
      15             : import 'package:flutter_localizations/flutter_localizations.dart';
      16             : import 'package:provider/provider.dart';
      17             : import 'package:window_manager/window_manager.dart';
      18             : import 'cwtch/cwtch.dart';
      19             : import 'cwtch/cwtchNotifier.dart';
      20             : import 'l10n/custom_material_delegate.dart';
      21             : import 'licenses.dart';
      22             : import 'models/appstate.dart';
      23             : import 'models/contactlist.dart';
      24             : import 'models/profile.dart';
      25             : import 'models/profilelist.dart';
      26             : import 'models/servers.dart';
      27             : import 'views/profilemgrview.dart';
      28             : import 'views/splashView.dart';
      29             : import 'dart:io' show Platform, exit;
      30             : import 'themes/opaque.dart';
      31             : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
      32             : import 'package:connectivity_plus/connectivity_plus.dart';
      33             : 
      34           0 : var globalSettings = Settings(Locale("en", ''));
      35           0 : var globalErrorHandler = ErrorHandler();
      36           0 : var globalTorStatus = TorStatus();
      37           0 : var globalAppState = AppState();
      38           0 : var globalServersList = ServerListState();
      39             : 
      40           0 : Future<void> main() async {
      41           0 :   print("Cwtch version: ${EnvironmentConfig.BUILD_VER} built on: ${EnvironmentConfig.BUILD_DATE}");
      42           0 :   LicenseRegistry.addLicense(() => licenses());
      43           0 :   WidgetsFlutterBinding.ensureInitialized();
      44             :   // window_manager requires (await recommended but probably not required if not using immediately)
      45           0 :   windowManager.ensureInitialized();
      46           0 :   print("runApp()");
      47           0 :   return runApp(Flwtch());
      48             : }
      49             : 
      50             : class Flwtch extends StatefulWidget {
      51             :   final Key flwtch = GlobalKey();
      52             : 
      53           0 :   @override
      54           0 :   FlwtchState createState() => FlwtchState();
      55             : }
      56             : 
      57             : enum ConnectivityState { assumed_online, confirmed_offline, confirmed_online }
      58             : 
      59             : class FlwtchState extends State<Flwtch> with WindowListener {
      60             :   late Cwtch cwtch;
      61             :   late ProfileListState profs;
      62             :   final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
      63             :   final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler');
      64             :   final MethodChannel shutdownLinuxMethodChannel = MethodChannel('im.cwtch.linux.shutdown');
      65             :   late StreamSubscription? connectivityStream;
      66             :   ConnectivityState connectivityState = ConnectivityState.assumed_online;
      67             : 
      68             :   final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
      69             : 
      70           0 :   Future<dynamic> shutdownDirect(MethodCall call) async {
      71           0 :     EnvironmentConfig.debugLog("$call");
      72           0 :     await cwtch.Shutdown();
      73           0 :     return Future.value({});
      74             :   }
      75             : 
      76           0 :   @override
      77             :   initState() {
      78           0 :     print("initState() started, setting up handlers");
      79           0 :     globalSettings = Settings(Locale("en", ''));
      80           0 :     globalErrorHandler = ErrorHandler();
      81           0 :     globalTorStatus = TorStatus();
      82           0 :     globalAppState = AppState();
      83           0 :     globalServersList = ServerListState();
      84             : 
      85           0 :     print("initState: running...");
      86           0 :     windowManager.addListener(this);
      87             : 
      88           0 :     print("initState: registering notification, shutdown handlers...");
      89           0 :     profs = ProfileListState();
      90           0 :     notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
      91           0 :     shutdownMethodChannel.setMethodCallHandler(modalShutdown);
      92           0 :     shutdownLinuxMethodChannel.setMethodCallHandler(shutdownDirect);
      93           0 :     print("initState: creating cwtchnotifier, ffi");
      94           0 :     if (Platform.isAndroid) {
      95           0 :       var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList, this);
      96           0 :       cwtch = CwtchGomobile(cwtchNotifier);
      97           0 :     } else if (Platform.isLinux) {
      98             :       var cwtchNotifier =
      99           0 :           new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
     100           0 :       cwtch = CwtchFfi(cwtchNotifier);
     101             :     } else {
     102             :       var cwtchNotifier =
     103           0 :           new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
     104           0 :       cwtch = CwtchFfi(cwtchNotifier);
     105             :     }
     106             :     // Cwtch.start can take time, we don't want it blocking first splash screen draw, so postpone a smidge to let splash render
     107           0 :     Future.delayed(const Duration(milliseconds: 100), () {
     108           0 :       print("initState delayed: invoking cwtch.Start()");
     109           0 :       cwtch.Start().then((v) {
     110           0 :         cwtch.getCwtchDir().then((dir) {
     111           0 :           globalSettings.themeloader.LoadThemes(dir);
     112             :         });
     113             :       });
     114             :     });
     115           0 :     print("initState: starting connectivityListener");
     116           0 :     if (EnvironmentConfig.TEST_MODE == false) {
     117           0 :       startConnectivityListener();
     118             :     } else {
     119           0 :       connectivityStream = null;
     120             :     }
     121           0 :     print("initState: done!");
     122           0 :     super.initState();
     123             :   }
     124             : 
     125             :   // connectivity listening is an optional enhancement feature that tries to listen for OS events about the network
     126             :   // and if it detects coming back online, restarts the ACN/tor
     127             :   // gracefully fails and NOPs, as it's not a required functionality
     128           0 :   startConnectivityListener() async {
     129             :     try {
     130           0 :       connectivityStream = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
     131             :         // Got a new connectivity status!
     132           0 :         if (result == ConnectivityResult.none) {
     133           0 :           connectivityState = ConnectivityState.confirmed_offline;
     134             :         } else {
     135             :           // were we offline?
     136           0 :           if (connectivityState == ConnectivityState.confirmed_offline) {
     137           0 :             EnvironmentConfig.debugLog("Network appears to have come back online, restarting Tor");
     138           0 :             cwtch.ResetTor();
     139             :           }
     140           0 :           connectivityState = ConnectivityState.confirmed_online;
     141             :         }
     142           0 :       }, onError: (Object error) {
     143           0 :         print("Error listening to connectivity for network state: {$error}");
     144             :         return null;
     145             :       }, cancelOnError: true);
     146             :     } catch (e) {
     147           0 :       print("Warning: Unable to open connectivity for listening to network state: {$e}");
     148           0 :       connectivityStream = null;
     149             :     }
     150             :   }
     151             : 
     152           0 :   ChangeNotifierProvider<TorStatus> getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);
     153           0 :   ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler);
     154           0 :   ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings);
     155           0 :   ChangeNotifierProvider<AppState> getAppStateProvider() => ChangeNotifierProvider.value(value: globalAppState);
     156           0 :   Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
     157           0 :   ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
     158           0 :   ChangeNotifierProvider<ServerListState> getServerListStateProvider() => ChangeNotifierProvider.value(value: globalServersList);
     159             : 
     160           0 :   @override
     161             :   Widget build(BuildContext context) {
     162           0 :     globalSettings.initPackageInfo();
     163             : 
     164           0 :     return MultiProvider(
     165           0 :       providers: [
     166           0 :         getFlwtchStateProvider(),
     167           0 :         getProfileListProvider(),
     168           0 :         getSettingsProvider(),
     169           0 :         getErrorHandlerProvider(),
     170           0 :         getTorStatusProvider(),
     171           0 :         getAppStateProvider(),
     172           0 :         getServerListStateProvider(),
     173             :       ],
     174           0 :       builder: (context, widget) {
     175           0 :         return Consumer2<Settings, AppState>(
     176           0 :           builder: (context, settings, appState, child) => MaterialApp(
     177           0 :             key: Key('app'),
     178           0 :             navigatorKey: navKey,
     179           0 :             locale: settings.locale,
     180           0 :             showPerformanceOverlay: settings.profileMode,
     181           0 :             localizationsDelegates: <LocalizationsDelegate<dynamic>>[
     182             :               AppLocalizations.delegate,
     183           0 :               MaterialLocalizationDelegate(),
     184             :               GlobalMaterialLocalizations.delegate,
     185             :               GlobalCupertinoLocalizations.delegate,
     186             :               GlobalWidgetsLocalizations.delegate,
     187             :             ],
     188             :             supportedLocales: AppLocalizations.supportedLocales,
     189             :             title: 'Cwtch',
     190           0 :             showSemanticsDebugger: settings.useSemanticDebugger,
     191           0 :             theme: mkThemeData(settings),
     192           0 :             home: (!appState.loaded) ? SplashView() : ProfileMgrView(),
     193             :           ),
     194             :         );
     195             :       },
     196             :     );
     197             :   }
     198             : 
     199             :   // invoked from either ProfileManagerView's appbar close button, or a ShutdownClicked event on
     200             :   // the MyBroadcastReceiver method channel
     201           0 :   Future<void> modalShutdown(MethodCall mc) async {
     202             :     // set up the buttons
     203           0 :     Widget cancelButton = ElevatedButton(
     204           0 :       child: Text(AppLocalizations.of(navKey.currentContext!)!.cancel),
     205           0 :       onPressed: () {
     206           0 :         Navigator.of(navKey.currentContext!).pop(); // dismiss dialog
     207             :       },
     208             :     );
     209           0 :     Widget continueButton = ElevatedButton(
     210           0 :         child: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchAction),
     211           0 :         onPressed: () {
     212           0 :           Provider.of<AppState>(navKey.currentContext!, listen: false).cwtchIsClosing = true;
     213           0 :           Navigator.of(navKey.currentContext!).pop(); // dismiss dialog
     214             :         });
     215             : 
     216             :     // set up the AlertDialog
     217           0 :     AlertDialog alert = AlertDialog(
     218           0 :       title: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchDialogTitle),
     219           0 :       content: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchDialog),
     220           0 :       actions: [
     221             :         cancelButton,
     222             :         continueButton,
     223             :       ],
     224             :     );
     225             : 
     226             :     // show the dialog
     227           0 :     showDialog(
     228           0 :       context: navKey.currentContext!,
     229             :       barrierDismissible: false,
     230           0 :       builder: (BuildContext context) {
     231             :         return alert;
     232             :       },
     233           0 :     ).then((val) {
     234           0 :       if (Provider.of<AppState>(navKey.currentContext!, listen: false).cwtchIsClosing) {
     235           0 :         globalAppState.SetModalState(ModalState.shutdown);
     236             :         // Directly call the shutdown command, Android will do this for us...
     237           0 :         Provider.of<FlwtchState>(navKey.currentContext!, listen: false).shutdown();
     238             :       }
     239             :     });
     240             :   }
     241             : 
     242           0 :   Future<void> shutdown() async {
     243           0 :     globalAppState.SetModalState(ModalState.shutdown);
     244           0 :     EnvironmentConfig.debugLog("shutting down");
     245           0 :     await cwtch.Shutdown();
     246             :     // Wait a few seconds as shutting down things takes a little time..
     247             :     {
     248           0 :       if (Platform.isAndroid) {
     249           0 :         SystemNavigator.pop();
     250           0 :       } else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
     251           0 :         print("Exiting...");
     252           0 :         exit(0);
     253             :       }
     254             :     }
     255             :   }
     256             : 
     257             :   // Invoked via notificationClickChannel by MyBroadcastReceiver in MainActivity.kt
     258             :   // coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
     259           0 :   Future<void> _externalNotificationClicked(MethodCall call) async {
     260           0 :     var args = jsonDecode(call.arguments);
     261           0 :     _notificationSelectConvo(args["ProfileOnion"], args["Handle"]);
     262             :   }
     263             : 
     264           0 :   Future<void> _notificationSelectConvo(String profileOnion, int convoId) async {
     265           0 :     var profile = profs.getProfile(profileOnion)!;
     266           0 :     var convo = profile.contactList.getContact(convoId)!;
     267           0 :     if (profileOnion.isEmpty) {
     268             :       return;
     269             :     }
     270           0 :     Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
     271           0 :     convo.unreadMessages = 0;
     272             : 
     273             :     // Clear nav path back to root
     274           0 :     while (navKey.currentState!.canPop()) {
     275           0 :       navKey.currentState!.pop();
     276             :     }
     277             : 
     278           0 :     Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = null;
     279           0 :     Provider.of<AppState>(navKey.currentContext!, listen: false).selectedProfile = profileOnion;
     280           0 :     Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = convoId;
     281             : 
     282           0 :     Navigator.of(navKey.currentContext!).push(
     283           0 :       PageRouteBuilder(
     284           0 :         settings: RouteSettings(name: "conversations"),
     285           0 :         pageBuilder: (c, a1, a2) {
     286           0 :           return OrientationBuilder(builder: (orientationBuilderContext, orientation) {
     287           0 :             return MultiProvider(
     288           0 :                 providers: [ChangeNotifierProvider<ProfileInfoState>.value(value: profile), ChangeNotifierProvider<ContactListState>.value(value: profile.contactList)],
     289           0 :                 builder: (innercontext, widget) {
     290           0 :                   var appState = Provider.of<AppState>(navKey.currentContext!);
     291           0 :                   var settings = Provider.of<Settings>(navKey.currentContext!);
     292           0 :                   return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : MessageView();
     293             :                 });
     294             :           });
     295             :         },
     296           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     297           0 :         transitionDuration: Duration(milliseconds: 200),
     298             :       ),
     299             :     );
     300             :     // On Gnome follows up a clicked notification with a "Cwtch is ready" notification that takes you to the app. AFAICT just because Gnome is bad
     301             :     // https://askubuntu.com/questions/1286206/how-to-skip-the-is-ready-notification-and-directly-open-apps-in-ubuntu-20-4
     302           0 :     await windowManager.show();
     303           0 :     await windowManager.focus();
     304             :   }
     305             : 
     306             :   // using windowManager flutter plugin until proper lifecycle management lands in desktop
     307             : 
     308           0 :   @override
     309             :   void onWindowFocus() {
     310           0 :     globalAppState.focus = true;
     311             :   }
     312             : 
     313           0 :   @override
     314             :   void onWindowBlur() {
     315           0 :     globalAppState.focus = false;
     316             :   }
     317             : 
     318           0 :   void onWindowClose() {
     319           0 :     Provider.of<FlwtchState>(navKey.currentContext!, listen: false).shutdown();
     320             :   }
     321             : 
     322           0 :   @override
     323             :   void dispose() {
     324           0 :     globalAppState.SetModalState(ModalState.shutdown);
     325           0 :     cwtch.Shutdown();
     326           0 :     windowManager.removeListener(this);
     327           0 :     cwtch.dispose();
     328           0 :     connectivityStream?.cancel();
     329           0 :     super.dispose();
     330             :   }
     331             : }

Generated by: LCOV version 1.14