LCOV - code coverage report
Current view: top level - lib/views - addeditservers.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 191 0.0 %
Date: 2024-09-12 20:27:43 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:cwtch/cwtch/cwtch.dart';
       2             : import 'package:cwtch/cwtch_icons_icons.dart';
       3             : import 'package:cwtch/models/servers.dart';
       4             : import 'package:cwtch/widgets/cwtchlabel.dart';
       5             : import 'package:cwtch/widgets/passwordfield.dart';
       6             : import 'package:cwtch/widgets/textfield.dart';
       7             : import 'package:flutter/material.dart';
       8             : import 'package:cwtch/settings.dart';
       9             : import 'package:provider/provider.dart';
      10             : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
      11             : 
      12             : import '../errorHandler.dart';
      13             : import '../main.dart';
      14             : 
      15             : /// Pane to add or edit a server
      16             : class AddEditServerView extends StatefulWidget {
      17           0 :   const AddEditServerView();
      18             : 
      19           0 :   @override
      20           0 :   _AddEditServerViewState createState() => _AddEditServerViewState();
      21             : }
      22             : 
      23             : class _AddEditServerViewState extends State<AddEditServerView> {
      24             :   final _formKey = GlobalKey<FormState>();
      25             : 
      26             :   final ctrlrDesc = TextEditingController(text: "");
      27             :   final ctrlrOldPass = TextEditingController(text: "");
      28             :   final ctrlrPass = TextEditingController(text: "");
      29             :   final ctrlrPass2 = TextEditingController(text: "");
      30             :   final ctrlrOnion = TextEditingController(text: "");
      31             : 
      32             :   late bool usePassword;
      33             : 
      34           0 :   @override
      35             :   void initState() {
      36           0 :     super.initState();
      37           0 :     var serverInfoState = Provider.of<ServerInfoState>(context, listen: false);
      38           0 :     ctrlrOnion.text = serverInfoState.onion;
      39           0 :     usePassword = serverInfoState.isEncrypted;
      40           0 :     if (serverInfoState.description.isNotEmpty) {
      41           0 :       ctrlrDesc.text = serverInfoState.description;
      42             :     }
      43             :   }
      44             : 
      45           0 :   @override
      46             :   void dispose() {
      47           0 :     super.dispose();
      48             :   }
      49             : 
      50           0 :   @override
      51             :   Widget build(BuildContext context) {
      52           0 :     return Scaffold(
      53           0 :       appBar: AppBar(
      54           0 :         title: ctrlrOnion.text.isEmpty ? Text(AppLocalizations.of(context)!.addServerTitle) : Text(AppLocalizations.of(context)!.editServerTitle),
      55             :       ),
      56           0 :       body: _buildSettingsList(),
      57             :     );
      58             :   }
      59             : 
      60           0 :   void _handleSwitchPassword(bool? value) {
      61           0 :     setState(() {
      62           0 :       usePassword = value!;
      63             :     });
      64             :   }
      65             : 
      66           0 :   Widget _buildSettingsList() {
      67           0 :     ScrollController controller = ScrollController();
      68           0 :     return Consumer2<ServerInfoState, Settings>(builder: (context, serverInfoState, settings, child) {
      69           0 :       return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
      70           0 :         return Scrollbar(
      71             :             trackVisibility: true,
      72             :             controller: controller,
      73           0 :             child: SingleChildScrollView(
      74             :                 controller: controller,
      75             :                 clipBehavior: Clip.antiAlias,
      76           0 :                 child: ConstrainedBox(
      77           0 :                     constraints: BoxConstraints(
      78           0 :                       minHeight: viewportConstraints.maxHeight,
      79             :                     ),
      80           0 :                     child: Form(
      81           0 :                         key: _formKey,
      82           0 :                         child: Container(
      83           0 :                             margin: EdgeInsets.fromLTRB(30, 5, 30, 10),
      84           0 :                             padding: EdgeInsets.fromLTRB(20, 5, 20, 10),
      85           0 :                             child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
      86             :                               // Onion
      87           0 :                               Visibility(
      88           0 :                                   visible: serverInfoState.onion.isNotEmpty,
      89           0 :                                   child: Column(
      90             :                                       mainAxisAlignment: MainAxisAlignment.start,
      91             :                                       crossAxisAlignment: CrossAxisAlignment.start,
      92           0 :                                       children: [CwtchLabel(label: AppLocalizations.of(context)!.serverAddress), SelectableText(serverInfoState.onion)])),
      93             : 
      94             :                               // Description
      95           0 :                               Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
      96           0 :                                 SizedBox(
      97             :                                   height: 20,
      98             :                                 ),
      99           0 :                                 CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
     100           0 :                                 Text(AppLocalizations.of(context)!.serverDescriptionDescription),
     101           0 :                                 SizedBox(
     102             :                                   height: 20,
     103             :                                 ),
     104           0 :                                 CwtchTextField(
     105           0 :                                   controller: ctrlrDesc,
     106           0 :                                   hintText: AppLocalizations.of(context)!.fieldDescriptionLabel,
     107             :                                   autofocus: false,
     108             :                                 )
     109             :                               ]),
     110             : 
     111           0 :                               SizedBox(
     112             :                                 height: 20,
     113             :                               ),
     114             : 
     115             :                               // Enabled
     116           0 :                               Visibility(
     117           0 :                                   visible: serverInfoState.onion.isNotEmpty,
     118           0 :                                   child: SwitchListTile(
     119           0 :                                     title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor)),
     120           0 :                                     subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription),
     121           0 :                                     value: serverInfoState.running,
     122           0 :                                     onChanged: (bool value) {
     123           0 :                                       serverInfoState.setRunning(value);
     124             :                                       if (value) {
     125           0 :                                         Provider.of<FlwtchState>(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
     126             :                                       } else {
     127           0 :                                         Provider.of<FlwtchState>(context, listen: false).cwtch.StopServer(serverInfoState.onion);
     128             :                                       }
     129             :                                     },
     130           0 :                                     activeTrackColor: settings.theme.defaultButtonColor,
     131           0 :                                     inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
     132           0 :                                     secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor),
     133             :                                   )),
     134             : 
     135             :                               // Auto start
     136           0 :                               SwitchListTile(
     137           0 :                                 title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor)),
     138           0 :                                 subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription),
     139           0 :                                 value: serverInfoState.autoStart,
     140           0 :                                 onChanged: (bool value) {
     141           0 :                                   serverInfoState.setAutostart(value);
     142             : 
     143           0 :                                   if (serverInfoState.onion.isNotEmpty) {
     144           0 :                                     Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
     145             :                                   }
     146             :                                 },
     147           0 :                                 activeTrackColor: settings.theme.defaultButtonColor,
     148           0 :                                 inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
     149           0 :                                 secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor),
     150             :                               ),
     151             : 
     152             :                               // metrics
     153           0 :                               Visibility(
     154           0 :                                   visible: serverInfoState.onion.isNotEmpty && serverInfoState.running,
     155           0 :                                   child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
     156           0 :                                     SizedBox(
     157             :                                       height: 20,
     158             :                                     ),
     159           0 :                                     Text(
     160           0 :                                       AppLocalizations.of(context)!.serverMetricsLabel,
     161             :                                     ),
     162           0 :                                     Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
     163           0 :                                       Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
     164           0 :                                         Text(AppLocalizations.of(context)!.serverTotalMessagesLabel),
     165             :                                       ]),
     166           0 :                                       Text(serverInfoState.totalMessages.toString())
     167             :                                     ]),
     168           0 :                                     Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
     169           0 :                                       Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
     170           0 :                                         Text(AppLocalizations.of(context)!.serverConnectionsLabel),
     171             :                                       ]),
     172           0 :                                       Text(serverInfoState.connections.toString())
     173             :                                     ]),
     174             :                                   ])),
     175             : 
     176             :                               // ***** Password *****
     177             : 
     178             :                               // use password toggle
     179           0 :                               Visibility(
     180           0 :                                   visible: serverInfoState.onion.isEmpty,
     181           0 :                                   child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
     182           0 :                                     SizedBox(
     183             :                                       height: 20,
     184             :                                     ),
     185           0 :                                     Checkbox(
     186           0 :                                       value: usePassword,
     187           0 :                                       fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor),
     188           0 :                                       activeColor: settings.current().defaultButtonActiveColor,
     189           0 :                                       onChanged: _handleSwitchPassword,
     190             :                                     ),
     191           0 :                                     Text(
     192           0 :                                       AppLocalizations.of(context)!.radioUsePassword,
     193           0 :                                       style: TextStyle(color: settings.current().mainTextColor),
     194             :                                     ),
     195           0 :                                     SizedBox(
     196             :                                       height: 20,
     197             :                                     ),
     198           0 :                                     Padding(
     199           0 :                                         padding: EdgeInsets.symmetric(horizontal: 24),
     200           0 :                                         child: Text(
     201           0 :                                           usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription,
     202             :                                           textAlign: TextAlign.center,
     203             :                                         )),
     204           0 :                                     SizedBox(
     205             :                                       height: 20,
     206             :                                     ),
     207             :                                   ])),
     208             : 
     209             :                               // current password
     210           0 :                               Visibility(
     211           0 :                                   visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted,
     212           0 :                                   child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
     213           0 :                                     CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
     214           0 :                                     SizedBox(
     215             :                                       height: 20,
     216             :                                     ),
     217           0 :                                     CwtchPasswordField(
     218           0 :                                       controller: ctrlrOldPass,
     219           0 :                                       autoFillHints: [AutofillHints.newPassword],
     220           0 :                                       validator: (value) {
     221             :                                         // Password field can be empty when just updating the profile, not on creation
     222           0 :                                         if (serverInfoState.isEncrypted && serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
     223           0 :                                           return AppLocalizations.of(context)!.passwordErrorEmpty;
     224             :                                         }
     225           0 :                                         if (Provider.of<ErrorHandler>(context).deletedServerError == true) {
     226           0 :                                           return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer;
     227             :                                         }
     228             :                                         return null;
     229             :                                       },
     230             :                                     ),
     231           0 :                                     SizedBox(
     232             :                                       height: 20,
     233             :                                     ),
     234             :                                   ])),
     235             : 
     236             :                               // new passwords 1 & 2
     237           0 :                               Visibility(
     238             :                                 // Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check
     239           0 :                                 visible: serverInfoState.onion.isEmpty && usePassword,
     240           0 :                                 child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
     241           0 :                                   CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
     242           0 :                                   SizedBox(
     243             :                                     height: 20,
     244             :                                   ),
     245           0 :                                   CwtchPasswordField(
     246           0 :                                     controller: ctrlrPass,
     247           0 :                                     validator: (value) {
     248             :                                       // Password field can be empty when just updating the profile, not on creation
     249           0 :                                       if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
     250           0 :                                         return AppLocalizations.of(context)!.passwordErrorEmpty;
     251             :                                       }
     252           0 :                                       if (value != ctrlrPass2.value.text) {
     253           0 :                                         return AppLocalizations.of(context)!.passwordErrorMatch;
     254             :                                       }
     255             :                                       return null;
     256             :                                     },
     257             :                                   ),
     258           0 :                                   SizedBox(
     259             :                                     height: 20,
     260             :                                   ),
     261           0 :                                   CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
     262           0 :                                   SizedBox(
     263             :                                     height: 20,
     264             :                                   ),
     265           0 :                                   CwtchPasswordField(
     266           0 :                                       controller: ctrlrPass2,
     267           0 :                                       validator: (value) {
     268             :                                         // Password field can be empty when just updating the profile, not on creation
     269           0 :                                         if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
     270           0 :                                           return AppLocalizations.of(context)!.passwordErrorEmpty;
     271             :                                         }
     272           0 :                                         if (value != ctrlrPass.value.text) {
     273           0 :                                           return AppLocalizations.of(context)!.passwordErrorMatch;
     274             :                                         }
     275             :                                         return null;
     276             :                                       }),
     277             :                                 ]),
     278             :                               ),
     279             : 
     280           0 :                               SizedBox(
     281             :                                 height: 20,
     282             :                               ),
     283             : 
     284           0 :                               Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
     285           0 :                                 ElevatedButton(
     286           0 :                                   onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed,
     287           0 :                                   child: Text(
     288           0 :                                     serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton,
     289             :                                     textAlign: TextAlign.center,
     290             :                                   ),
     291             :                                 ),
     292             :                               ]),
     293           0 :                               Visibility(
     294           0 :                                   visible: serverInfoState.onion.isNotEmpty,
     295           0 :                                   child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
     296           0 :                                     SizedBox(
     297             :                                       height: 20,
     298             :                                     ),
     299           0 :                                     Tooltip(
     300           0 :                                         message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer,
     301           0 :                                         child: ElevatedButton.icon(
     302           0 :                                           onPressed: () {
     303           0 :                                             showAlertDialog(context);
     304             :                                           },
     305           0 :                                           icon: Icon(Icons.delete_forever),
     306           0 :                                           label: Text(AppLocalizations.of(context)!.deleteBtn),
     307             :                                         ))
     308             :                                   ]))
     309             : 
     310             :                               // ***** END Password *****
     311             :                             ]))))));
     312             :       });
     313             :     });
     314             :   }
     315             : 
     316           0 :   void _createPressed() {
     317             :     // This will run all the validations in the form including
     318             :     // checking that display name is not empty, and an actual check that the passwords
     319             :     // match (and are provided if the user has requested an encrypted profile).
     320           0 :     if (_formKey.currentState!.validate()) {
     321           0 :       if (usePassword) {
     322           0 :         Provider.of<FlwtchState>(context, listen: false).cwtch.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
     323             :       } else {
     324           0 :         Provider.of<FlwtchState>(context, listen: false).cwtch.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
     325             :       }
     326           0 :       Navigator.of(context).pop();
     327             :     }
     328             :   }
     329             : 
     330           0 :   void _savePressed() {
     331           0 :     var server = Provider.of<ServerInfoState>(context, listen: false);
     332             : 
     333           0 :     Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
     334           0 :     server.setDescription(ctrlrDesc.text);
     335             : 
     336           0 :     if (_formKey.currentState!.validate()) {
     337             :       // TODO support change password
     338             :     }
     339           0 :     Navigator.of(context).pop();
     340             :   }
     341             : 
     342           0 :   showAlertDialog(BuildContext context) {
     343             :     // set up the buttons
     344           0 :     Widget cancelButton = ElevatedButton(
     345           0 :       child: Text(AppLocalizations.of(context)!.cancel),
     346           0 :       onPressed: () {
     347           0 :         Navigator.of(context).pop(); // dismiss dialog
     348             :       },
     349             :     );
     350           0 :     Widget continueButton = ElevatedButton(
     351           0 :         child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
     352           0 :         onPressed: () {
     353           0 :           var onion = Provider.of<ServerInfoState>(context, listen: false).onion;
     354           0 :           Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteServer(onion, Provider.of<ServerInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
     355           0 :           Future.delayed(
     356             :             const Duration(milliseconds: 500),
     357           0 :             () {
     358           0 :               if (globalErrorHandler.deletedServerSuccess) {
     359           0 :                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteServerSuccess + ":" + onion));
     360           0 :                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     361           0 :                 Navigator.of(context).popUntil((route) => route.settings.name == "servers"); // dismiss dialog
     362             :               } else {
     363           0 :                 Navigator.of(context).pop();
     364             :               }
     365             :             },
     366             :           );
     367             :         });
     368             :     // set up the AlertDialog
     369           0 :     AlertDialog alert = AlertDialog(
     370           0 :       title: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
     371           0 :       actions: [
     372             :         cancelButton,
     373             :         continueButton,
     374             :       ],
     375             :     );
     376             : 
     377             :     // show the dialog
     378           0 :     showDialog(
     379             :       context: context,
     380           0 :       builder: (BuildContext context) {
     381             :         return alert;
     382             :       },
     383             :     );
     384             :   }
     385             : }

Generated by: LCOV version 1.14