LCOV - code coverage report
Current view: top level - lib - main.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 165 0.0 %
Date: 2024-04-05 23:55:39 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();
     110           0 :       cwtch.getCwtchDir().then((dir) {
     111           0 :         globalSettings.themeloader.LoadThemes(dir);
     112             :       });
     113             :     });
     114           0 :     print("initState: starting connectivityListener");
     115           0 :     if (EnvironmentConfig.TEST_MODE == false) {
     116           0 :       startConnectivityListener();
     117             :     } else {
     118           0 :       connectivityStream = null;
     119             :     }
     120           0 :     print("initState: done!");
     121           0 :     super.initState();
     122             :   }
     123             : 
     124             :   // connectivity listening is an optional enhancement feature that tries to listen for OS events about the network
     125             :   // and if it detects coming back online, restarts the ACN/tor
     126             :   // gracefully fails and NOPs, as it's not a required functionality
     127           0 :   startConnectivityListener() async {
     128             :     try {
     129           0 :       connectivityStream = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
     130             :         // Got a new connectivity status!
     131           0 :         if (result == ConnectivityResult.none) {
     132           0 :           connectivityState = ConnectivityState.confirmed_offline;
     133             :         } else {
     134             :           // were we offline?
     135           0 :           if (connectivityState == ConnectivityState.confirmed_offline) {
     136           0 :             EnvironmentConfig.debugLog("Network appears to have come back online, restarting Tor");
     137           0 :             cwtch.ResetTor();
     138             :           }
     139           0 :           connectivityState = ConnectivityState.confirmed_online;
     140             :         }
     141           0 :       }, onError: (Object error) {
     142           0 :         print("Error listening to connectivity for network state: {$error}");
     143             :         return null;
     144             :       }, cancelOnError: true);
     145             :     } catch (e) {
     146           0 :       print("Warning: Unable to open connectivity for listening to network state: {$e}");
     147           0 :       connectivityStream = null;
     148             :     }
     149             :   }
     150             : 
     151           0 :   ChangeNotifierProvider<TorStatus> getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);
     152           0 :   ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler);
     153           0 :   ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings);
     154           0 :   ChangeNotifierProvider<AppState> getAppStateProvider() => ChangeNotifierProvider.value(value: globalAppState);
     155           0 :   Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
     156           0 :   ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
     157           0 :   ChangeNotifierProvider<ServerListState> getServerListStateProvider() => ChangeNotifierProvider.value(value: globalServersList);
     158             : 
     159           0 :   @override
     160             :   Widget build(BuildContext context) {
     161           0 :     globalSettings.initPackageInfo();
     162             : 
     163           0 :     return MultiProvider(
     164           0 :       providers: [
     165           0 :         getFlwtchStateProvider(),
     166           0 :         getProfileListProvider(),
     167           0 :         getSettingsProvider(),
     168           0 :         getErrorHandlerProvider(),
     169           0 :         getTorStatusProvider(),
     170           0 :         getAppStateProvider(),
     171           0 :         getServerListStateProvider(),
     172             :       ],
     173           0 :       builder: (context, widget) {
     174           0 :         return Consumer2<Settings, AppState>(
     175           0 :           builder: (context, settings, appState, child) => MaterialApp(
     176           0 :             key: Key('app'),
     177           0 :             navigatorKey: navKey,
     178           0 :             locale: settings.locale,
     179           0 :             showPerformanceOverlay: settings.profileMode,
     180           0 :             localizationsDelegates: <LocalizationsDelegate<dynamic>>[
     181             :               AppLocalizations.delegate,
     182           0 :               MaterialLocalizationDelegate(),
     183             :               GlobalMaterialLocalizations.delegate,
     184             :               GlobalCupertinoLocalizations.delegate,
     185             :               GlobalWidgetsLocalizations.delegate,
     186             :             ],
     187             :             supportedLocales: AppLocalizations.supportedLocales,
     188             :             title: 'Cwtch',
     189           0 :             showSemanticsDebugger: settings.useSemanticDebugger,
     190           0 :             theme: mkThemeData(settings),
     191           0 :             home: (!appState.loaded) ? SplashView() : ProfileMgrView(),
     192             :           ),
     193             :         );
     194             :       },
     195             :     );
     196             :   }
     197             : 
     198             :   // invoked from either ProfileManagerView's appbar close button, or a ShutdownClicked event on
     199             :   // the MyBroadcastReceiver method channel
     200           0 :   Future<void> modalShutdown(MethodCall mc) async {
     201             :     // set up the buttons
     202           0 :     Widget cancelButton = ElevatedButton(
     203           0 :       child: Text(AppLocalizations.of(navKey.currentContext!)!.cancel),
     204           0 :       onPressed: () {
     205           0 :         Navigator.of(navKey.currentContext!).pop(); // dismiss dialog
     206             :       },
     207             :     );
     208           0 :     Widget continueButton = ElevatedButton(
     209           0 :         child: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchAction),
     210           0 :         onPressed: () {
     211           0 :           Provider.of<AppState>(navKey.currentContext!, listen: false).cwtchIsClosing = true;
     212           0 :           Navigator.of(navKey.currentContext!).pop(); // dismiss dialog
     213             :         });
     214             : 
     215             :     // set up the AlertDialog
     216           0 :     AlertDialog alert = AlertDialog(
     217           0 :       title: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchDialogTitle),
     218           0 :       content: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchDialog),
     219           0 :       actions: [
     220             :         cancelButton,
     221             :         continueButton,
     222             :       ],
     223             :     );
     224             : 
     225             :     // show the dialog
     226           0 :     showDialog(
     227           0 :       context: navKey.currentContext!,
     228             :       barrierDismissible: false,
     229           0 :       builder: (BuildContext context) {
     230             :         return alert;
     231             :       },
     232           0 :     ).then((val) {
     233           0 :       if (Provider.of<AppState>(navKey.currentContext!, listen: false).cwtchIsClosing) {
     234           0 :         globalAppState.SetModalState(ModalState.shutdown);
     235             :         // Directly call the shutdown command, Android will do this for us...
     236           0 :         Provider.of<FlwtchState>(navKey.currentContext!, listen: false).shutdown();
     237             :       }
     238             :     });
     239             :   }
     240             : 
     241           0 :   Future<void> shutdown() async {
     242           0 :     globalAppState.SetModalState(ModalState.shutdown);
     243           0 :     EnvironmentConfig.debugLog("shutting down");
     244           0 :     await cwtch.Shutdown();
     245             :     // Wait a few seconds as shutting down things takes a little time..
     246             :     {
     247           0 :       if (Platform.isAndroid) {
     248           0 :         SystemNavigator.pop();
     249           0 :       } else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
     250           0 :         print("Exiting...");
     251           0 :         exit(0);
     252             :       }
     253             :     }
     254             :   }
     255             : 
     256             :   // Invoked via notificationClickChannel by MyBroadcastReceiver in MainActivity.kt
     257             :   // coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
     258           0 :   Future<void> _externalNotificationClicked(MethodCall call) async {
     259           0 :     var args = jsonDecode(call.arguments);
     260           0 :     _notificationSelectConvo(args["ProfileOnion"], args["Handle"]);
     261             :   }
     262             : 
     263           0 :   Future<void> _notificationSelectConvo(String profileOnion, int convoId) async {
     264           0 :     var profile = profs.getProfile(profileOnion)!;
     265           0 :     var convo = profile.contactList.getContact(convoId)!;
     266           0 :     if (profileOnion.isEmpty) {
     267             :       return;
     268             :     }
     269           0 :     Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
     270           0 :     convo.unreadMessages = 0;
     271             : 
     272             :     // Clear nav path back to root
     273           0 :     while (navKey.currentState!.canPop()) {
     274           0 :       navKey.currentState!.pop();
     275             :     }
     276             : 
     277           0 :     Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = null;
     278           0 :     Provider.of<AppState>(navKey.currentContext!, listen: false).selectedProfile = profileOnion;
     279           0 :     Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = convoId;
     280             : 
     281           0 :     Navigator.of(navKey.currentContext!).push(
     282           0 :       PageRouteBuilder(
     283           0 :         settings: RouteSettings(name: "conversations"),
     284           0 :         pageBuilder: (c, a1, a2) {
     285           0 :           return OrientationBuilder(builder: (orientationBuilderContext, orientation) {
     286           0 :             return MultiProvider(
     287           0 :                 providers: [ChangeNotifierProvider<ProfileInfoState>.value(value: profile), ChangeNotifierProvider<ContactListState>.value(value: profile.contactList)],
     288           0 :                 builder: (innercontext, widget) {
     289           0 :                   var appState = Provider.of<AppState>(navKey.currentContext!);
     290           0 :                   var settings = Provider.of<Settings>(navKey.currentContext!);
     291           0 :                   return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : MessageView();
     292             :                 });
     293             :           });
     294             :         },
     295           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     296           0 :         transitionDuration: Duration(milliseconds: 200),
     297             :       ),
     298             :     );
     299             :     // 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
     300             :     // https://askubuntu.com/questions/1286206/how-to-skip-the-is-ready-notification-and-directly-open-apps-in-ubuntu-20-4
     301           0 :     await windowManager.show();
     302           0 :     await windowManager.focus();
     303             :   }
     304             : 
     305             :   // using windowManager flutter plugin until proper lifecycle management lands in desktop
     306             : 
     307           0 :   @override
     308             :   void onWindowFocus() {
     309           0 :     globalAppState.focus = true;
     310             :   }
     311             : 
     312           0 :   @override
     313             :   void onWindowBlur() {
     314           0 :     globalAppState.focus = false;
     315             :   }
     316             : 
     317           0 :   void onWindowClose() {}
     318             : 
     319           0 :   @override
     320             :   void dispose() {
     321           0 :     globalAppState.SetModalState(ModalState.shutdown);
     322           0 :     cwtch.Shutdown();
     323           0 :     windowManager.removeListener(this);
     324           0 :     cwtch.dispose();
     325           0 :     connectivityStream?.cancel();
     326           0 :     super.dispose();
     327             :   }
     328             : }

Generated by: LCOV version 1.14