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-10-08 18:18:56 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 :                             color: settings.theme.backgroundPaneColor,
      84             :                             //margin: EdgeInsets.fromLTRB(30, 5, 30, 10),
      85           0 :                             padding: EdgeInsets.fromLTRB(50, 10, 50, 20),
      86           0 :                             child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
      87             :                               // Onion
      88           0 :                               Visibility(
      89           0 :                                   visible: serverInfoState.onion.isNotEmpty,
      90           0 :                                   child: Column(
      91             :                                       mainAxisAlignment: MainAxisAlignment.start,
      92             :                                       crossAxisAlignment: CrossAxisAlignment.start,
      93           0 :                                       children: [CwtchLabel(label: AppLocalizations.of(context)!.serverAddress), SelectableText(serverInfoState.onion)])),
      94             : 
      95             :                               // Description
      96           0 :                               Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
      97           0 :                                 SizedBox(
      98             :                                   height: 20,
      99             :                                 ),
     100           0 :                                 CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
     101           0 :                                 Text(AppLocalizations.of(context)!.serverDescriptionDescription),
     102           0 :                                 SizedBox(
     103             :                                   height: 20,
     104             :                                 ),
     105           0 :                                 CwtchTextField(
     106           0 :                                   controller: ctrlrDesc,
     107           0 :                                   hintText: AppLocalizations.of(context)!.fieldDescriptionLabel,
     108             :                                   autofocus: false,
     109             :                                 )
     110             :                               ]),
     111             : 
     112           0 :                               SizedBox(
     113             :                                 height: 20,
     114             :                               ),
     115             : 
     116             :                               // Enabled
     117           0 :                               Visibility(
     118           0 :                                   visible: serverInfoState.onion.isNotEmpty,
     119           0 :                                   child: SwitchListTile(
     120           0 :                                     title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor)),
     121           0 :                                     subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription),
     122           0 :                                     value: serverInfoState.running,
     123           0 :                                     onChanged: (bool value) {
     124           0 :                                       serverInfoState.setRunning(value);
     125             :                                       if (value) {
     126           0 :                                         Provider.of<FlwtchState>(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
     127             :                                       } else {
     128           0 :                                         Provider.of<FlwtchState>(context, listen: false).cwtch.StopServer(serverInfoState.onion);
     129             :                                       }
     130             :                                     },
     131           0 :                                     activeTrackColor: settings.theme.defaultButtonColor,
     132           0 :                                     inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
     133           0 :                                     secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor),
     134             :                                   )),
     135             : 
     136             :                               // Auto start
     137           0 :                               SwitchListTile(
     138           0 :                                 title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor)),
     139           0 :                                 subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription),
     140           0 :                                 value: serverInfoState.autoStart,
     141           0 :                                 onChanged: (bool value) {
     142           0 :                                   serverInfoState.setAutostart(value);
     143             : 
     144           0 :                                   if (serverInfoState.onion.isNotEmpty) {
     145           0 :                                     Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
     146             :                                   }
     147             :                                 },
     148           0 :                                 activeTrackColor: settings.theme.defaultButtonColor,
     149           0 :                                 inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
     150           0 :                                 secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor),
     151             :                               ),
     152             : 
     153             :                               // metrics
     154           0 :                               Visibility(
     155           0 :                                   visible: serverInfoState.onion.isNotEmpty && serverInfoState.running,
     156           0 :                                   child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
     157           0 :                                     SizedBox(
     158             :                                       height: 20,
     159             :                                     ),
     160           0 :                                     Text(
     161           0 :                                       AppLocalizations.of(context)!.serverMetricsLabel,
     162             :                                     ),
     163           0 :                                     Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
     164           0 :                                       Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
     165           0 :                                         Text(AppLocalizations.of(context)!.serverTotalMessagesLabel),
     166             :                                       ]),
     167           0 :                                       Text(serverInfoState.totalMessages.toString())
     168             :                                     ]),
     169           0 :                                     Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
     170           0 :                                       Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
     171           0 :                                         Text(AppLocalizations.of(context)!.serverConnectionsLabel),
     172             :                                       ]),
     173           0 :                                       Text(serverInfoState.connections.toString())
     174             :                                     ]),
     175             :                                   ])),
     176             : 
     177             :                               // ***** Password *****
     178             : 
     179             :                               // use password toggle
     180           0 :                               Visibility(
     181           0 :                                   visible: serverInfoState.onion.isEmpty,
     182           0 :                                   child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
     183           0 :                                     SizedBox(
     184             :                                       height: 20,
     185             :                                     ),
     186           0 :                                     Checkbox(
     187           0 :                                       value: usePassword,
     188           0 :                                       fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor),
     189           0 :                                       activeColor: settings.current().defaultButtonActiveColor,
     190           0 :                                       onChanged: _handleSwitchPassword,
     191             :                                     ),
     192           0 :                                     Text(
     193           0 :                                       AppLocalizations.of(context)!.radioUsePassword,
     194           0 :                                       style: TextStyle(color: settings.current().mainTextColor),
     195             :                                     ),
     196           0 :                                     SizedBox(
     197             :                                       height: 20,
     198             :                                     ),
     199           0 :                                     Padding(
     200           0 :                                         padding: EdgeInsets.symmetric(horizontal: 24),
     201           0 :                                         child: Text(
     202           0 :                                           usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription,
     203             :                                           textAlign: TextAlign.center,
     204             :                                         )),
     205           0 :                                     SizedBox(
     206             :                                       height: 20,
     207             :                                     ),
     208             :                                   ])),
     209             : 
     210             :                               // current password
     211           0 :                               Visibility(
     212           0 :                                   visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted,
     213           0 :                                   child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
     214           0 :                                     CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
     215           0 :                                     SizedBox(
     216             :                                       height: 20,
     217             :                                     ),
     218           0 :                                     CwtchPasswordField(
     219           0 :                                       controller: ctrlrOldPass,
     220           0 :                                       autoFillHints: [AutofillHints.newPassword],
     221           0 :                                       validator: (value) {
     222             :                                         // Password field can be empty when just updating the profile, not on creation
     223           0 :                                         if (serverInfoState.isEncrypted && serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
     224           0 :                                           return AppLocalizations.of(context)!.passwordErrorEmpty;
     225             :                                         }
     226           0 :                                         if (Provider.of<ErrorHandler>(context).deletedServerError == true) {
     227           0 :                                           return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer;
     228             :                                         }
     229             :                                         return null;
     230             :                                       },
     231             :                                     ),
     232           0 :                                     SizedBox(
     233             :                                       height: 20,
     234             :                                     ),
     235             :                                   ])),
     236             : 
     237             :                               // new passwords 1 & 2
     238           0 :                               Visibility(
     239             :                                 // 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
     240           0 :                                 visible: serverInfoState.onion.isEmpty && usePassword,
     241           0 :                                 child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
     242           0 :                                   CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
     243           0 :                                   SizedBox(
     244             :                                     height: 20,
     245             :                                   ),
     246           0 :                                   CwtchPasswordField(
     247           0 :                                     controller: ctrlrPass,
     248           0 :                                     validator: (value) {
     249             :                                       // Password field can be empty when just updating the profile, not on creation
     250           0 :                                       if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
     251           0 :                                         return AppLocalizations.of(context)!.passwordErrorEmpty;
     252             :                                       }
     253           0 :                                       if (value != ctrlrPass2.value.text) {
     254           0 :                                         return AppLocalizations.of(context)!.passwordErrorMatch;
     255             :                                       }
     256             :                                       return null;
     257             :                                     },
     258             :                                   ),
     259           0 :                                   SizedBox(
     260             :                                     height: 20,
     261             :                                   ),
     262           0 :                                   CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
     263           0 :                                   SizedBox(
     264             :                                     height: 20,
     265             :                                   ),
     266           0 :                                   CwtchPasswordField(
     267           0 :                                       controller: ctrlrPass2,
     268           0 :                                       validator: (value) {
     269             :                                         // Password field can be empty when just updating the profile, not on creation
     270           0 :                                         if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
     271           0 :                                           return AppLocalizations.of(context)!.passwordErrorEmpty;
     272             :                                         }
     273           0 :                                         if (value != ctrlrPass.value.text) {
     274           0 :                                           return AppLocalizations.of(context)!.passwordErrorMatch;
     275             :                                         }
     276             :                                         return null;
     277             :                                       }),
     278             :                                 ]),
     279             :                               ),
     280             : 
     281           0 :                               SizedBox(
     282             :                                 height: 20,
     283             :                               ),
     284             : 
     285           0 :                               Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
     286           0 :                                 ElevatedButton(
     287           0 :                                   onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed,
     288           0 :                                   child: Text(
     289           0 :                                     serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton,
     290             :                                     textAlign: TextAlign.center,
     291             :                                   ),
     292             :                                 ),
     293             :                               ]),
     294           0 :                               Visibility(
     295           0 :                                   visible: serverInfoState.onion.isNotEmpty,
     296           0 :                                   child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
     297           0 :                                     SizedBox(
     298             :                                       height: 20,
     299             :                                     ),
     300           0 :                                     Tooltip(
     301           0 :                                         message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer,
     302           0 :                                         child: ElevatedButton.icon(
     303           0 :                                           onPressed: () {
     304           0 :                                             showAlertDialog(context);
     305             :                                           },
     306           0 :                                           icon: Icon(Icons.delete_forever),
     307           0 :                                           label: Text(AppLocalizations.of(context)!.deleteBtn),
     308             :                                         ))
     309             :                                   ]))
     310             : 
     311             :                               // ***** END Password *****
     312             :                             ]))))));
     313             :       });
     314             :     });
     315             :   }
     316             : 
     317           0 :   void _createPressed() {
     318             :     // This will run all the validations in the form including
     319             :     // checking that display name is not empty, and an actual check that the passwords
     320             :     // match (and are provided if the user has requested an encrypted profile).
     321           0 :     if (_formKey.currentState!.validate()) {
     322           0 :       if (usePassword) {
     323           0 :         Provider.of<FlwtchState>(context, listen: false).cwtch.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
     324             :       } else {
     325           0 :         Provider.of<FlwtchState>(context, listen: false).cwtch.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
     326             :       }
     327           0 :       Navigator.of(context).pop();
     328             :     }
     329             :   }
     330             : 
     331           0 :   void _savePressed() {
     332           0 :     var server = Provider.of<ServerInfoState>(context, listen: false);
     333             : 
     334           0 :     Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
     335           0 :     server.setDescription(ctrlrDesc.text);
     336             : 
     337           0 :     if (_formKey.currentState!.validate()) {
     338             :       // TODO support change password
     339             :     }
     340           0 :     Navigator.of(context).pop();
     341             :   }
     342             : 
     343           0 :   showAlertDialog(BuildContext context) {
     344             :     // set up the buttons
     345           0 :     Widget cancelButton = ElevatedButton(
     346           0 :       child: Text(AppLocalizations.of(context)!.cancel),
     347           0 :       onPressed: () {
     348           0 :         Navigator.of(context).pop(); // dismiss dialog
     349             :       },
     350             :     );
     351           0 :     Widget continueButton = ElevatedButton(
     352           0 :         child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
     353           0 :         onPressed: () {
     354           0 :           var onion = Provider.of<ServerInfoState>(context, listen: false).onion;
     355           0 :           Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteServer(onion, Provider.of<ServerInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
     356           0 :           Future.delayed(
     357             :             const Duration(milliseconds: 500),
     358           0 :             () {
     359           0 :               if (globalErrorHandler.deletedServerSuccess) {
     360           0 :                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteServerSuccess + ":" + onion));
     361           0 :                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     362           0 :                 Navigator.of(context).popUntil((route) => route.settings.name == "servers"); // dismiss dialog
     363             :               } else {
     364           0 :                 Navigator.of(context).pop();
     365             :               }
     366             :             },
     367             :           );
     368             :         });
     369             :     // set up the AlertDialog
     370           0 :     AlertDialog alert = AlertDialog(
     371           0 :       title: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
     372           0 :       actions: [
     373             :         cancelButton,
     374             :         continueButton,
     375             :       ],
     376             :     );
     377             : 
     378             :     // show the dialog
     379           0 :     showDialog(
     380             :       context: context,
     381           0 :       builder: (BuildContext context) {
     382             :         return alert;
     383             :       },
     384             :     );
     385             :   }
     386             : }

Generated by: LCOV version 1.14