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-04-05 23:55:39 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 :       tooltip: Provider.of<TorStatus>(context).progress == 100
      93           0 :           ? AppLocalizations.of(context)!.networkStatusOnline
      94           0 :           : (Provider.of<TorStatus>(context).progress == 0 ? AppLocalizations.of(context)!.networkStatusDisconnected : AppLocalizations.of(context)!.networkStatusAttemptingTor),
      95             :     ));
      96             : 
      97             :     // Unlock Profiles
      98           0 :     actions.add(IconButton(
      99           0 :       icon: Icon(CwtchIcons.lock_open_24px),
     100           0 :       splashRadius: Material.defaultSplashRadius / 2,
     101           0 :       color: Provider.of<ProfileListState>(context).profiles.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor : Provider.of<Settings>(context).theme.mainTextColor,
     102           0 :       tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
     103           0 :       onPressed: _modalUnlockProfiles,
     104             :     ));
     105             : 
     106             :     // Servers
     107           0 :     if (Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() &&
     108           0 :         Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) &&
     109           0 :         !Platform.isAndroid &&
     110           0 :         !Platform.isIOS) {
     111           0 :       actions.add(
     112           0 :           IconButton(icon: Icon(CwtchIcons.dns_black_24dp), splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
     113             :     }
     114             : 
     115             :     // Global Settings
     116           0 :     actions.add(IconButton(
     117           0 :         key: Key("OpenSettingsView"),
     118           0 :         icon: Icon(Icons.settings),
     119           0 :         tooltip: AppLocalizations.of(context)!.tooltipOpenSettings,
     120           0 :         splashRadius: Material.defaultSplashRadius / 2,
     121           0 :         onPressed: _pushGlobalSettings));
     122             : 
     123             :     // shutdown cwtch
     124           0 :     actions.add(IconButton(icon: Icon(Icons.close), tooltip: AppLocalizations.of(context)!.shutdownCwtchTooltip, splashRadius: Material.defaultSplashRadius / 2, onPressed: _modalShutdown));
     125             : 
     126             :     return actions;
     127             :   }
     128             : 
     129           0 :   void _modalShutdown() {
     130           0 :     Provider.of<FlwtchState>(context, listen: false).modalShutdown(MethodCall(""));
     131             :   }
     132             : 
     133           0 :   void _pushGlobalSettings() {
     134           0 :     Navigator.of(context).push(
     135           0 :       PageRouteBuilder(
     136           0 :         pageBuilder: (bcontext, a1, a2) {
     137           0 :           return Provider(
     138           0 :             create: (_) => Provider.of<FlwtchState>(bcontext, listen: false),
     139           0 :             child: GlobalSettingsView(),
     140             :           );
     141             :         },
     142           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     143           0 :         transitionDuration: Duration(milliseconds: 200),
     144             :       ),
     145             :     );
     146             :   }
     147             : 
     148           0 :   void _pushServers() {
     149           0 :     Navigator.of(context).push(
     150           0 :       PageRouteBuilder(
     151           0 :         settings: RouteSettings(name: "servers"),
     152           0 :         pageBuilder: (bcontext, a1, a2) {
     153           0 :           return MultiProvider(
     154           0 :             providers: [ChangeNotifierProvider.value(value: globalServersList), Provider.value(value: Provider.of<FlwtchState>(context))],
     155           0 :             child: ServersView(),
     156             :           );
     157             :         },
     158           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     159           0 :         transitionDuration: Duration(milliseconds: 200),
     160             :       ),
     161             :     );
     162             :   }
     163             : 
     164           0 :   void _pushTorStatus() {
     165           0 :     Navigator.of(context).push(
     166           0 :       PageRouteBuilder(
     167           0 :         settings: RouteSettings(name: "torconfig"),
     168           0 :         pageBuilder: (bcontext, a1, a2) {
     169           0 :           return MultiProvider(
     170           0 :             providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
     171           0 :             child: TorStatusView(),
     172             :           );
     173             :         },
     174           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     175           0 :         transitionDuration: Duration(milliseconds: 200),
     176             :       ),
     177             :     );
     178             :   }
     179             : 
     180           0 :   void _pushAddProfile(bcontext, {onion = ""}) {
     181           0 :     Navigator.popUntil(bcontext, (route) => route.isFirst);
     182             : 
     183           0 :     Navigator.of(context).push(
     184           0 :       PageRouteBuilder(
     185           0 :         pageBuilder: (bcontext, a1, a2) {
     186           0 :           return MultiProvider(
     187           0 :             providers: [
     188           0 :               ChangeNotifierProvider<ProfileInfoState>(
     189           0 :                 create: (_) => ProfileInfoState(onion: onion),
     190             :               ),
     191             :             ],
     192           0 :             builder: (context, widget) => AddEditProfileView(key: Key('addprofile')),
     193             :           );
     194             :         },
     195           0 :         transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
     196           0 :         transitionDuration: Duration(milliseconds: 200),
     197             :       ),
     198             :     );
     199             :   }
     200             : 
     201           0 :   void _modalAddImportProfiles() {
     202           0 :     showModalBottomSheet<void>(
     203           0 :         context: context,
     204             :         isScrollControlled: true,
     205           0 :         builder: (BuildContext context) {
     206           0 :           return Padding(
     207           0 :               padding: MediaQuery.of(context).viewInsets,
     208           0 :               child: RepaintBoundary(
     209           0 :                   child: Container(
     210           0 :                 height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
     211           0 :                 child: Center(
     212           0 :                     child: Padding(
     213           0 :                         padding: EdgeInsets.all(10.0),
     214           0 :                         child: Column(
     215             :                           mainAxisAlignment: MainAxisAlignment.center,
     216             :                           crossAxisAlignment: CrossAxisAlignment.center,
     217             :                           mainAxisSize: MainAxisSize.min,
     218           0 :                           children: <Widget>[
     219           0 :                             SizedBox(
     220             :                               height: 20,
     221             :                             ),
     222           0 :                             Expanded(
     223           0 :                                 child: ElevatedButton(
     224           0 :                               style: ElevatedButton.styleFrom(
     225           0 :                                 minimumSize: Size(399, 20),
     226           0 :                                 maximumSize: Size(400, 20),
     227           0 :                                 shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     228             :                               ),
     229           0 :                               child: Text(
     230           0 :                                 key: Key("addNewProfileActual"),
     231           0 :                                 AppLocalizations.of(context)!.addProfileTitle,
     232           0 :                                 semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
     233           0 :                                 style: TextStyle(fontWeight: FontWeight.bold),
     234             :                               ),
     235           0 :                               onPressed: () {
     236           0 :                                 _pushAddProfile(context);
     237             :                               },
     238             :                             )),
     239           0 :                             SizedBox(
     240             :                               height: 20,
     241             :                             ),
     242           0 :                             Expanded(
     243           0 :                                 child: Tooltip(
     244           0 :                                     message: AppLocalizations.of(context)!.importProfileTooltip,
     245           0 :                                     child: ElevatedButton(
     246           0 :                                       style: ElevatedButton.styleFrom(
     247           0 :                                         minimumSize: Size(399, 20),
     248           0 :                                         backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
     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(AppLocalizations.of(context)!.importProfile,
     255           0 :                                           semanticsLabel: AppLocalizations.of(context)!.importProfile,
     256           0 :                                           style: TextStyle(color: Provider.of<Settings>(context).theme.mainTextColor, fontWeight: FontWeight.bold)),
     257           0 :                                       onPressed: () {
     258             :                                         // 10GB profiles should be enough for anyone?
     259           0 :                                         showFilePicker(context, MaxGeneralFileSharingSize, (file) {
     260           0 :                                           showPasswordDialog(context, AppLocalizations.of(context)!.importProfile, AppLocalizations.of(context)!.importProfile, (password) {
     261           0 :                                             Navigator.popUntil(context, (route) => route.isFirst);
     262           0 :                                             Provider.of<FlwtchState>(context, listen: false).cwtch.ImportProfile(file.path, password).then((value) {
     263           0 :                                               if (value == "") {
     264           0 :                                                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullyImportedProfile.replaceFirst("%profile", file.path)));
     265           0 :                                                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     266             :                                               } else {
     267           0 :                                                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.failedToImportProfile));
     268           0 :                                                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     269             :                                               }
     270             :                                             });
     271             :                                           });
     272           0 :                                         }, () {}, () {});
     273             :                                       },
     274             :                                     ))),
     275           0 :                             SizedBox(
     276             :                               height: 20,
     277             :                             ),
     278             :                           ],
     279             :                         ))),
     280             :               )));
     281             :         });
     282             :   }
     283             : 
     284           0 :   void _modalUnlockProfiles() {
     285           0 :     showModalBottomSheet<void>(
     286           0 :         context: context,
     287             :         isScrollControlled: true,
     288           0 :         builder: (BuildContext context) {
     289           0 :           return Padding(
     290           0 :               padding: MediaQuery.of(context).viewInsets,
     291           0 :               child: RepaintBoundary(
     292           0 :                   child: Container(
     293           0 :                 height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
     294           0 :                 child: Center(
     295           0 :                     child: Padding(
     296           0 :                         padding: EdgeInsets.all(10.0),
     297           0 :                         child: Column(
     298             :                           mainAxisAlignment: MainAxisAlignment.center,
     299             :                           mainAxisSize: MainAxisSize.min,
     300           0 :                           children: <Widget>[
     301           0 :                             Text(AppLocalizations.of(context)!.enterProfilePassword),
     302           0 :                             SizedBox(
     303             :                               height: 20,
     304             :                             ),
     305           0 :                             CwtchPasswordField(
     306           0 :                               key: Key("unlockPasswordProfileElement"),
     307             :                               autofocus: true,
     308           0 :                               controller: ctrlrPassword,
     309           0 :                               action: unlock,
     310           0 :                               validator: (value) {
     311             :                                 return null;
     312             :                               },
     313             :                             ),
     314           0 :                             SizedBox(
     315             :                               height: 20,
     316             :                             ),
     317           0 :                             Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
     318           0 :                               Spacer(),
     319           0 :                               Expanded(
     320           0 :                                   child: ElevatedButton(
     321           0 :                                 child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
     322           0 :                                 onPressed: () {
     323           0 :                                   unlock(ctrlrPassword.value.text);
     324             :                                 },
     325             :                               )),
     326           0 :                               Spacer()
     327             :                             ]),
     328             :                           ],
     329             :                         ))),
     330             :               )));
     331             :         });
     332             :   }
     333             : 
     334           0 :   void unlock(String password) {
     335           0 :     Provider.of<FlwtchState>(context, listen: false).cwtch.LoadProfiles(password);
     336           0 :     ctrlrPassword.text = "";
     337           0 :     Navigator.pop(context);
     338             :   }
     339             : 
     340           0 :   Widget _buildProfileManager() {
     341           0 :     return Consumer<ProfileListState>(
     342           0 :       builder: (context, pls, child) {
     343           0 :         var tiles = pls.profiles.map(
     344           0 :           (ProfileInfoState profile) {
     345           0 :             return ChangeNotifierProvider<ProfileInfoState>.value(
     346             :               value: profile,
     347           0 :               builder: (context, child) => ProfileRow(),
     348             :             );
     349             :           },
     350             :         );
     351             : 
     352           0 :         List<ChangeNotifierProvider<ProfileInfoState>> widgetTiles = tiles.toList(growable: true);
     353           0 :         widgetTiles.add(ChangeNotifierProvider<ProfileInfoState>.value(
     354           0 :             value: ProfileInfoState(onion: ""),
     355           0 :             builder: (context, child) {
     356           0 :               return Container(
     357           0 :                   margin: EdgeInsets.only(top: 20),
     358           0 :                   width: MediaQuery.of(context).size.width,
     359           0 :                   child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
     360           0 :                     Tooltip(
     361           0 :                         message: AppLocalizations.of(context)!.tooltipUnlockProfiles,
     362           0 :                         child: TextButton.icon(
     363           0 :                           icon: Icon(CwtchIcons.lock_open_24px, color: Provider.of<Settings>(context).current().defaultButtonTextColor),
     364           0 :                           style: TextButton.styleFrom(
     365           0 :                             minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 80),
     366           0 :                             maximumSize: Size(MediaQuery.of(context).size.width * 0.8, 80),
     367           0 :                             shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     368             :                           ),
     369           0 :                           label: Text(
     370           0 :                             AppLocalizations.of(context)!.unlock,
     371           0 :                             semanticsLabel: AppLocalizations.of(context)!.unlock,
     372           0 :                             style: TextStyle(fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).current().defaultButtonTextColor),
     373             :                           ),
     374           0 :                           onPressed: () {
     375           0 :                             _modalUnlockProfiles();
     376             :                           },
     377             :                         )),
     378             :                   ]));
     379             :             }));
     380             : 
     381           0 :         final divided = ListTile.divideTiles(
     382             :           context: context,
     383           0 :           color: Provider.of<Settings>(context).theme.backgroundPaneColor,
     384             :           tiles: widgetTiles,
     385           0 :         ).toList();
     386             : 
     387             :         // Display the welcome message / unlock profiles button to new accounts
     388           0 :         if (tiles.isEmpty) {
     389           0 :           return Center(
     390           0 :               child: Column(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
     391           0 :             Text(AppLocalizations.of(context)!.unlockProfileTip, textAlign: TextAlign.center),
     392           0 :             Container(
     393           0 :                 width: MediaQuery.of(context).size.width,
     394           0 :                 margin: EdgeInsets.only(top: 20),
     395           0 :                 child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
     396           0 :                   Tooltip(
     397           0 :                       message: AppLocalizations.of(context)!.addProfileTitle,
     398           0 :                       child: TextButton.icon(
     399           0 :                         icon: Icon(Icons.add, color: Provider.of<Settings>(context).current().mainTextColor),
     400           0 :                         style: TextButton.styleFrom(
     401           0 :                           minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 80),
     402           0 :                           maximumSize: Size(MediaQuery.of(context).size.width * 0.8, 80),
     403           0 :                           shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     404             :                         ),
     405           0 :                         label: Text(
     406           0 :                           AppLocalizations.of(context)!.addProfileTitle,
     407           0 :                           semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
     408           0 :                           style: TextStyle(fontWeight: FontWeight.bold),
     409             :                         ),
     410           0 :                         onPressed: () {
     411           0 :                           _modalAddImportProfiles();
     412             :                         },
     413             :                       )),
     414             :                 ])),
     415           0 :             widgetTiles[0]
     416             :           ]));
     417             :         }
     418             : 
     419           0 :         return ListView(children: divided);
     420             :       },
     421             :     );
     422             :   }
     423             : }

Generated by: LCOV version 1.14