LCOV - code coverage report
Current view: top level - lib/views - profilemgrview.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 247 0.0 %
Date: 2024-10-08 18:18:56 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:io';
       2             : 
       3             : import 'package:cwtch/constants.dart';
       4             : import 'package:cwtch/controllers/enter_password.dart';
       5             : import 'package:cwtch/controllers/filesharing.dart';
       6             : import 'package:cwtch/cwtch_icons_icons.dart';
       7             : import 'package:cwtch/models/appstate.dart';
       8             : import 'package:cwtch/models/profile.dart';
       9             : import 'package:cwtch/models/profilelist.dart';
      10             : import 'package:flutter/material.dart';
      11             : import 'package:cwtch/settings.dart';
      12             : import 'package:cwtch/views/torstatusview.dart';
      13             : import 'package:cwtch/widgets/passwordfield.dart';
      14             : import 'package:cwtch/widgets/tor_icon.dart';
      15             : import 'package:flutter/services.dart';
      16             : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
      17             : import 'package:cwtch/widgets/profilerow.dart';
      18             : import 'package:provider/provider.dart';
      19             : import '../main.dart';
      20             : import '../torstatus.dart';
      21             : import 'addeditprofileview.dart';
      22             : import 'globalsettingsview.dart';
      23             : import 'serversview.dart';
      24             : 
      25             : class ProfileMgrView extends StatefulWidget {
      26           0 :   ProfileMgrView();
      27             : 
      28           0 :   @override
      29           0 :   _ProfileMgrViewState createState() => _ProfileMgrViewState();
      30             : }
      31             : 
      32             : class _ProfileMgrViewState extends State<ProfileMgrView> {
      33             :   final ctrlrPassword = TextEditingController();
      34             : 
      35           0 :   @override
      36             :   void dispose() {
      37           0 :     ctrlrPassword.dispose();
      38           0 :     super.dispose();
      39             :   }
      40             : 
      41           0 :   @override
      42             :   Widget build(BuildContext context) {
      43           0 :     return Consumer<Settings>(
      44             :       // Prevents Android back button from closing the app on the profile manager screen
      45             :       // (which would shutdown connections and all kinds of other expensive to generate things)
      46           0 :       builder: (context, settings, child) => WillPopScope(
      47           0 :           onWillPop: () async {
      48           0 :             _modalShutdown();
      49           0 :             return Provider.of<AppState>(context, listen: false).cwtchIsClosing;
      50             :           },
      51           0 :           child: Scaffold(
      52           0 :             key: Key("ProfileManagerView"),
      53           0 :             backgroundColor: settings.theme.backgroundMainColor,
      54           0 :             appBar: AppBar(
      55           0 :               title: Row(children: [
      56           0 :                 Icon(
      57             :                   CwtchIcons.cwtch_knott,
      58             :                   size: 36,
      59           0 :                   color: settings.theme.mainTextColor,
      60             :                 ),
      61           0 :                 SizedBox(
      62             :                   width: 10,
      63             :                 ),
      64           0 :                 Expanded(
      65           0 :                     child: Text(MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort,
      66           0 :                         style: TextStyle(color: settings.current().mainTextColor)))
      67             :               ]),
      68           0 :               actions: getActions(),
      69             :             ),
      70           0 :             floatingActionButton: FloatingActionButton(
      71           0 :               onPressed: _modalAddImportProfiles,
      72           0 :               tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
      73           0 :               child: Icon(
      74             :                 Icons.add,
      75           0 :                 semanticLabel: AppLocalizations.of(context)!.addNewProfileBtn,
      76           0 :                 color: Provider.of<Settings>(context).theme.defaultButtonTextColor,
      77             :               ),
      78             :             ),
      79           0 :             body: _buildProfileManager(),
      80             :           )),
      81             :     );
      82             :   }
      83             : 
      84           0 :   List<Widget> getActions() {
      85           0 :     List<Widget> actions = new List<Widget>.empty(growable: true);
      86             : 
      87             :     // Tor Status
      88           0 :     actions.add(IconButton(
      89           0 :       icon: TorIcon(),
      90           0 :       onPressed: _pushTorStatus,
      91           0 :       splashRadius: Material.defaultSplashRadius / 2,
      92           0 :       color: Provider.of<ProfileListState>(context).profiles.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor : Provider.of<Settings>(context).theme.mainTextColor,
      93           0 :       tooltip: Provider.of<TorStatus>(context).progress == 100
      94           0 :           ? AppLocalizations.of(context)!.networkStatusOnline
      95           0 :           : (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
      96             :     ));
      97             : 
      98             :     // Unlock Profiles
      99           0 :     actions.add(IconButton(
     100           0 :       icon: Icon(CwtchIcons.lock_open_24px),
     101           0 :       splashRadius: Material.defaultSplashRadius / 2,
     102           0 :       color: Provider.of<ProfileListState>(context).profiles.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor : Provider.of<Settings>(context).theme.mainTextColor,
     103           0 :       tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
     104           0 :       onPressed: _modalUnlockProfiles,
     105             :     ));
     106             : 
     107             :     // Servers
     108           0 :     if (Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() &&
     109           0 :         Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) &&
     110           0 :         !Platform.isAndroid &&
     111           0 :         !Platform.isIOS) {
     112           0 :       actions.add(
     113           0 :           IconButton(icon: Icon(CwtchIcons.dns_black_24dp), splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
     114             :     }
     115             : 
     116             :     // Global Settings
     117           0 :     actions.add(IconButton(
     118           0 :         key: Key("OpenSettingsView"),
     119           0 :         icon: Icon(Icons.settings),
     120           0 :         tooltip: AppLocalizations.of(context)!.tooltipOpenSettings,
     121           0 :         splashRadius: Material.defaultSplashRadius / 2,
     122           0 :         onPressed: _pushGlobalSettings));
     123             : 
     124             :     // shutdown cwtch
     125           0 :     actions.add(IconButton(icon: Icon(Icons.close), tooltip: AppLocalizations.of(context)!.shutdownCwtchTooltip, splashRadius: Material.defaultSplashRadius / 2, onPressed: _modalShutdown));
     126             : 
     127             :     return actions;
     128             :   }
     129             : 
     130           0 :   void _modalShutdown() {
     131           0 :     Provider.of<FlwtchState>(context, listen: false).modalShutdown(MethodCall(""));
     132             :   }
     133             : 
     134           0 :   void _pushGlobalSettings() {
     135           0 :     Navigator.of(context).push(
     136           0 :       PageRouteBuilder(
     137           0 :         pageBuilder: (bcontext, a1, a2) {
     138           0 :           return Provider(
     139           0 :             create: (_) => Provider.of<FlwtchState>(bcontext, listen: false),
     140           0 :             child: GlobalSettingsView(),
     141             :           );
     142             :         },
     143           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     144           0 :         transitionDuration: Duration(milliseconds: 200),
     145             :       ),
     146             :     );
     147             :   }
     148             : 
     149           0 :   void _pushServers() {
     150           0 :     Navigator.of(context).push(
     151           0 :       PageRouteBuilder(
     152           0 :         settings: RouteSettings(name: "servers"),
     153           0 :         pageBuilder: (bcontext, a1, a2) {
     154           0 :           return MultiProvider(
     155           0 :             providers: [ChangeNotifierProvider.value(value: globalServersList), Provider.value(value: Provider.of<FlwtchState>(context))],
     156           0 :             child: ServersView(),
     157             :           );
     158             :         },
     159           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     160           0 :         transitionDuration: Duration(milliseconds: 200),
     161             :       ),
     162             :     );
     163             :   }
     164             : 
     165           0 :   void _pushTorStatus() {
     166           0 :     Navigator.of(context).push(
     167           0 :       PageRouteBuilder(
     168           0 :         settings: RouteSettings(name: "torconfig"),
     169           0 :         pageBuilder: (bcontext, a1, a2) {
     170           0 :           return MultiProvider(
     171           0 :             providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
     172           0 :             child: TorStatusView(),
     173             :           );
     174             :         },
     175           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     176           0 :         transitionDuration: Duration(milliseconds: 200),
     177             :       ),
     178             :     );
     179             :   }
     180             : 
     181           0 :   void _pushAddProfile(bcontext, {onion = ""}) {
     182           0 :     Navigator.popUntil(bcontext, (route) => route.isFirst);
     183             : 
     184           0 :     Navigator.of(context).push(
     185           0 :       PageRouteBuilder(
     186           0 :         pageBuilder: (bcontext, a1, a2) {
     187           0 :           return MultiProvider(
     188           0 :             providers: [
     189           0 :               ChangeNotifierProvider<ProfileInfoState>(
     190           0 :                 create: (_) => ProfileInfoState(onion: onion),
     191             :               ),
     192             :             ],
     193           0 :             builder: (context, widget) => AddEditProfileView(key: Key('addprofile')),
     194             :           );
     195             :         },
     196           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     197           0 :         transitionDuration: Duration(milliseconds: 200),
     198             :       ),
     199             :     );
     200             :   }
     201             : 
     202           0 :   void _modalAddImportProfiles() {
     203           0 :     showModalBottomSheet<void>(
     204           0 :         context: context,
     205             :         isScrollControlled: true,
     206           0 :         builder: (BuildContext context) {
     207           0 :           return Padding(
     208           0 :               padding: MediaQuery.of(context).viewInsets,
     209           0 :               child: RepaintBoundary(
     210           0 :                   child: Container(
     211           0 :                 height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
     212           0 :                 child: Center(
     213           0 :                     child: Padding(
     214           0 :                         padding: EdgeInsets.all(10.0),
     215           0 :                         child: Column(
     216             :                           mainAxisAlignment: MainAxisAlignment.center,
     217             :                           crossAxisAlignment: CrossAxisAlignment.center,
     218             :                           mainAxisSize: MainAxisSize.min,
     219           0 :                           children: <Widget>[
     220           0 :                             SizedBox(
     221             :                               height: 20,
     222             :                             ),
     223           0 :                             Expanded(
     224           0 :                                 child: ElevatedButton(
     225           0 :                               style: ElevatedButton.styleFrom(
     226           0 :                                 minimumSize: Size(399, 20),
     227           0 :                                 maximumSize: Size(400, 20),
     228           0 :                                 shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     229             :                               ),
     230           0 :                               child: Text(
     231           0 :                                 key: Key("addNewProfileActual"),
     232           0 :                                 AppLocalizations.of(context)!.addProfileTitle,
     233           0 :                                 semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
     234           0 :                                 style: TextStyle(fontWeight: FontWeight.bold),
     235             :                               ),
     236           0 :                               onPressed: () {
     237           0 :                                 _pushAddProfile(context);
     238             :                               },
     239             :                             )),
     240           0 :                             SizedBox(
     241             :                               height: 20,
     242             :                             ),
     243           0 :                             Expanded(
     244           0 :                                 child: Tooltip(
     245           0 :                                     message: AppLocalizations.of(context)!.importProfileTooltip,
     246           0 :                                     child: OutlinedButton(
     247           0 :                                       style: ElevatedButton.styleFrom(
     248           0 :                                         minimumSize: Size(399, 20),
     249           0 :                                         maximumSize: Size(400, 20),
     250           0 :                                         shape: RoundedRectangleBorder(
     251           0 :                                             side: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, width: 2.0),
     252           0 :                                             borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     253             :                                       ),
     254           0 :                                       child: Text(
     255           0 :                                         AppLocalizations.of(context)!.importProfile,
     256           0 :                                         semanticsLabel: AppLocalizations.of(context)!.importProfile,
     257             :                                       ),
     258           0 :                                       onPressed: () {
     259             :                                         // 10GB profiles should be enough for anyone?
     260           0 :                                         showFilePicker(context, MaxGeneralFileSharingSize, (file) {
     261           0 :                                           showPasswordDialog(context, AppLocalizations.of(context)!.importProfile, AppLocalizations.of(context)!.importProfile, (password) {
     262           0 :                                             Navigator.popUntil(context, (route) => route.isFirst);
     263           0 :                                             Provider.of<FlwtchState>(context, listen: false).cwtch.ImportProfile(file.path, password).then((value) {
     264           0 :                                               if (value == "") {
     265           0 :                                                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullyImportedProfile.replaceFirst("%profile", file.path)));
     266           0 :                                                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     267             :                                               } else {
     268           0 :                                                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.failedToImportProfile));
     269           0 :                                                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     270             :                                               }
     271             :                                             });
     272             :                                           });
     273           0 :                                         }, () {}, () {});
     274             :                                       },
     275             :                                     ))),
     276           0 :                             SizedBox(
     277             :                               height: 20,
     278             :                             ),
     279             :                           ],
     280             :                         ))),
     281             :               )));
     282             :         });
     283             :   }
     284             : 
     285           0 :   void _modalUnlockProfiles() {
     286           0 :     showModalBottomSheet<void>(
     287           0 :         context: context,
     288             :         isScrollControlled: true,
     289           0 :         builder: (BuildContext context) {
     290           0 :           return Padding(
     291           0 :               padding: MediaQuery.of(context).viewInsets,
     292           0 :               child: RepaintBoundary(
     293           0 :                   child: Container(
     294           0 :                 height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
     295           0 :                 child: Center(
     296           0 :                     child: Padding(
     297           0 :                         padding: EdgeInsets.all(10.0),
     298           0 :                         child: Column(
     299             :                           mainAxisAlignment: MainAxisAlignment.center,
     300             :                           mainAxisSize: MainAxisSize.min,
     301           0 :                           children: <Widget>[
     302           0 :                             Text(AppLocalizations.of(context)!.enterProfilePassword),
     303           0 :                             SizedBox(
     304             :                               height: 20,
     305             :                             ),
     306           0 :                             CwtchPasswordField(
     307           0 :                               key: Key("unlockPasswordProfileElement"),
     308             :                               autofocus: true,
     309           0 :                               controller: ctrlrPassword,
     310           0 :                               action: unlock,
     311           0 :                               validator: (value) {
     312             :                                 return null;
     313             :                               },
     314             :                             ),
     315           0 :                             SizedBox(
     316             :                               height: 20,
     317             :                             ),
     318           0 :                             Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
     319           0 :                               Spacer(),
     320           0 :                               Expanded(
     321           0 :                                   child: ElevatedButton(
     322           0 :                                 child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
     323           0 :                                 onPressed: () {
     324           0 :                                   unlock(ctrlrPassword.value.text);
     325             :                                 },
     326             :                               )),
     327           0 :                               Spacer()
     328             :                             ]),
     329             :                           ],
     330             :                         ))),
     331             :               )));
     332             :         });
     333             :   }
     334             : 
     335           0 :   void unlock(String password) {
     336           0 :     Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(password);
     337           0 :     ctrlrPassword.text = "";
     338           0 :     Navigator.pop(context);
     339             :   }
     340             : 
     341           0 :   Widget _buildProfileManager() {
     342           0 :     return Consumer<ProfileListState>(
     343           0 :       builder: (context, pls, child) {
     344           0 :         var tiles = pls.profiles.map(
     345           0 :           (ProfileInfoState profile) {
     346           0 :             return ChangeNotifierProvider<ProfileInfoState>.value(
     347             :               value: profile,
     348           0 :               builder: (context, child) => ProfileRow(),
     349             :             );
     350             :           },
     351             :         );
     352             : 
     353           0 :         List<ChangeNotifierProvider<ProfileInfoState>> widgetTiles = tiles.toList(growable: true);
     354           0 :         widgetTiles.add(ChangeNotifierProvider<ProfileInfoState>.value(
     355           0 :             value: ProfileInfoState(onion: ""),
     356           0 :             builder: (context, child) {
     357           0 :               return Container(
     358           0 :                   margin: EdgeInsets.only(top: 20),
     359           0 :                   width: MediaQuery.of(context).size.width,
     360           0 :                   child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
     361           0 :                     Tooltip(
     362           0 :                         message: AppLocalizations.of(context)!.tooltipUnlockProfiles,
     363           0 :                         child: FilledButton.icon(
     364           0 :                           icon: Icon(CwtchIcons.lock_open_24px),
     365           0 :                           style: TextButton.styleFrom(
     366           0 :                             minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 80),
     367           0 :                             maximumSize: Size(MediaQuery.of(context).size.width * 0.8, 80),
     368           0 :                             shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     369             :                           ),
     370           0 :                           label: Text(
     371           0 :                             AppLocalizations.of(context)!.unlock,
     372           0 :                             semanticsLabel: AppLocalizations.of(context)!.unlock,
     373           0 :                             style: TextStyle(fontWeight: FontWeight.bold),
     374             :                           ),
     375           0 :                           onPressed: () {
     376           0 :                             _modalUnlockProfiles();
     377             :                           },
     378             :                         )),
     379             :                   ]));
     380             :             }));
     381             : 
     382           0 :         final divided = ListTile.divideTiles(
     383             :           context: context,
     384           0 :           color: Provider.of<Settings>(context).theme.backgroundMainColor,
     385             :           tiles: widgetTiles,
     386           0 :         ).toList();
     387             : 
     388             :         // Display the welcome message / unlock profiles button to new accounts
     389           0 :         if (tiles.isEmpty) {
     390           0 :           return Center(
     391           0 :               child: Column(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
     392           0 :             Text(AppLocalizations.of(context)!.unlockProfileTip, textAlign: TextAlign.center),
     393           0 :             Container(
     394           0 :                 width: MediaQuery.of(context).size.width,
     395           0 :                 margin: EdgeInsets.only(top: 20),
     396           0 :                 child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
     397           0 :                   Tooltip(
     398           0 :                       message: AppLocalizations.of(context)!.addProfileTitle,
     399           0 :                       child: FilledButton.icon(
     400           0 :                         icon: Icon(Icons.add),
     401           0 :                         style: TextButton.styleFrom(
     402           0 :                           minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 80),
     403           0 :                           maximumSize: Size(MediaQuery.of(context).size.width * 0.8, 80),
     404           0 :                           shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     405             :                         ),
     406           0 :                         label: Text(
     407           0 :                           AppLocalizations.of(context)!.addProfileTitle,
     408           0 :                           semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
     409           0 :                           style: TextStyle(fontWeight: FontWeight.bold),
     410             :                         ),
     411           0 :                         onPressed: () {
     412           0 :                           _modalAddImportProfiles();
     413             :                         },
     414             :                       )),
     415             :                 ])),
     416           0 :             widgetTiles[0]
     417             :           ]));
     418             :         }
     419             : 
     420           0 :         return ListView(children: divided);
     421             :       },
     422             :     );
     423             :   }
     424             : }

Generated by: LCOV version 1.14