LCOV - code coverage report
Current view: top level - lib/views - addeditprofileview.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 339 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/config.dart';
       4             : import 'package:cwtch/controllers/filesharing.dart';
       5             : import 'package:cwtch/cwtch/cwtch.dart';
       6             : import 'package:cwtch/models/appstate.dart';
       7             : import 'package:cwtch/models/profile.dart';
       8             : import 'package:cwtch/controllers/filesharing.dart' as filesharing;
       9             : import 'package:flutter/material.dart';
      10             : import 'package:flutter/services.dart';
      11             : import 'package:cwtch/widgets/buttontextfield.dart';
      12             : import 'package:cwtch/widgets/cwtchlabel.dart';
      13             : import 'package:cwtch/widgets/passwordfield.dart';
      14             : import 'package:cwtch/widgets/profileimage.dart';
      15             : import 'package:cwtch/widgets/textfield.dart';
      16             : import 'package:provider/provider.dart';
      17             : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
      18             : 
      19             : import '../constants.dart';
      20             : import '../cwtch_icons_icons.dart';
      21             : import '../errorHandler.dart';
      22             : import '../main.dart';
      23             : import '../settings.dart';
      24             : 
      25             : class AddEditProfileView extends StatefulWidget {
      26           0 :   const AddEditProfileView({Key? key}) : super(key: key);
      27             : 
      28           0 :   @override
      29           0 :   _AddEditProfileViewState createState() => _AddEditProfileViewState();
      30             : }
      31             : 
      32             : class _AddEditProfileViewState extends State<AddEditProfileView> {
      33             :   final _formKey = GlobalKey<FormState>();
      34             : 
      35             :   final ctrlrNick = TextEditingController(text: "");
      36             :   final ctrlrPrivateName = TextEditingController(text: "");
      37             :   final ctrlrOldPass = TextEditingController(text: "");
      38             :   final ctrlrPass = TextEditingController(text: "");
      39             :   final ctrlrPass2 = TextEditingController(text: "");
      40             :   final ctrlrOnion = TextEditingController(text: "");
      41             : 
      42             :   final ctrlrAttribute1 = TextEditingController(text: "");
      43             :   final ctrlrAttribute2 = TextEditingController(text: "");
      44             :   final ctrlrAttribute3 = TextEditingController(text: "");
      45             : 
      46             :   ScrollController controller = ScrollController();
      47             :   late bool usePassword;
      48             :   late bool deleted;
      49             : 
      50           0 :   @override
      51             :   void initState() {
      52           0 :     super.initState();
      53           0 :     usePassword = true;
      54           0 :     final nickname = Provider.of<ProfileInfoState>(context, listen: false).nickname;
      55           0 :     if (nickname.isNotEmpty) {
      56           0 :       ctrlrNick.text = nickname;
      57             :     }
      58           0 :     ctrlrPrivateName.text = Provider.of<ProfileInfoState>(context, listen: false).getPrivateName();
      59             :   }
      60             : 
      61           0 :   @override
      62             :   Widget build(BuildContext context) {
      63           0 :     ctrlrOnion.text = Provider.of<ProfileInfoState>(context).onion;
      64           0 :     return Scaffold(
      65           0 :       appBar: AppBar(
      66           0 :         title: Text(Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context)!.addProfileTitle : AppLocalizations.of(context)!.editProfileTitle),
      67             :       ),
      68           0 :       body: _buildForm(),
      69             :     );
      70             :   }
      71             : 
      72           0 :   void _handleSwitchPassword(bool? value) {
      73           0 :     setState(() {
      74           0 :       usePassword = value!;
      75             :     });
      76             :   }
      77             : 
      78             :   //  A few implementation notes
      79             :   // We use Visibility to hide optional structures when they are not requested.
      80             :   // We used SizedBox for inter-widget height padding in columns, otherwise elements can render a little too close together.
      81           0 :   Widget _buildForm() {
      82           0 :     return Consumer<Settings>(builder: (context, theme, child) {
      83           0 :       return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
      84           0 :         return Scrollbar(
      85             :             trackVisibility: true,
      86           0 :             controller: controller,
      87           0 :             child: SingleChildScrollView(
      88           0 :                 controller: controller,
      89             :                 clipBehavior: Clip.antiAlias,
      90           0 :                 child: ConstrainedBox(
      91           0 :                     constraints: BoxConstraints(
      92           0 :                       minHeight: viewportConstraints.maxHeight,
      93             :                     ),
      94           0 :                     child: Form(
      95           0 :                         key: _formKey,
      96           0 :                         child: Container(
      97           0 :                             color: theme.theme.backgroundPaneColor,
      98           0 :                             padding: EdgeInsets.all(50),
      99           0 :                             child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
     100           0 :                               Visibility(
     101           0 :                                   visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty,
     102           0 :                                   child: Column(
     103             :                                     crossAxisAlignment: CrossAxisAlignment.center,
     104             :                                     mainAxisAlignment: MainAxisAlignment.center,
     105             :                                     mainAxisSize: MainAxisSize.min,
     106           0 :                                     children: [
     107           0 :                                       Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
     108           0 :                                         MouseRegion(
     109           0 :                                             cursor: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? SystemMouseCursors.click : SystemMouseCursors.basic,
     110           0 :                                             child: GestureDetector(
     111             :                                                 // don't allow setting of profile images if the image previews experiment is disabled.
     112           0 :                                                 onTap: Provider.of<AppState>(context, listen: false).disableFilePicker ||
     113           0 :                                                         !Provider.of<Settings>(context, listen: false).isExperimentEnabled(ImagePreviewsExperiment)
     114             :                                                     ? null
     115           0 :                                                     : () {
     116           0 :                                                         filesharing.showFilePicker(context, MaxImageFileSharingSize, (File file) {
     117           0 :                                                           var profile = Provider.of<ProfileInfoState>(context, listen: false).onion;
     118             :                                                           // Share this image publicly (conversation handle == -1)
     119           0 :                                                           Provider.of<FlwtchState>(context, listen: false).cwtch.ShareFile(profile, -1, file.path);
     120             :                                                           // update the image cache locally
     121           0 :                                                           Provider.of<ProfileInfoState>(context, listen: false).imagePath = file.path;
     122           0 :                                                         }, () {
     123           0 :                                                           final snackBar = SnackBar(
     124           0 :                                                             content: Text(AppLocalizations.of(context)!.msgFileTooBig),
     125           0 :                                                             duration: Duration(seconds: 4),
     126             :                                                           );
     127           0 :                                                           ScaffoldMessenger.of(context).showSnackBar(snackBar);
     128           0 :                                                         }, () {});
     129             :                                                       },
     130           0 :                                                 child: ProfileImage(
     131           0 :                                                     imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
     132           0 :                                                         ? Provider.of<ProfileInfoState>(context).imagePath
     133           0 :                                                         : Provider.of<ProfileInfoState>(context).defaultImagePath,
     134             :                                                     diameter: 120,
     135           0 :                                                     tooltip: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
     136           0 :                                                         ? AppLocalizations.of(context)!.tooltipSelectACustomProfileImage
     137             :                                                         : "",
     138             :                                                     maskOut: false,
     139           0 :                                                     border: theme.theme.portraitOnlineBorderColor,
     140           0 :                                                     badgeTextColor: theme.theme.portraitContactBadgeTextColor,
     141           0 :                                                     badgeColor: theme.theme.portraitContactBadgeColor,
     142           0 :                                                     badgeEdit: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment))))
     143             :                                       ]),
     144           0 :                                       SizedBox(
     145           0 :                                           width: MediaQuery.of(context).size.width / 2,
     146           0 :                                           child: Column(
     147           0 :                                             children: [
     148           0 :                                               Padding(
     149           0 :                                                   padding: EdgeInsets.all(5.0),
     150           0 :                                                   child: CwtchTextField(
     151           0 :                                                       controller: ctrlrAttribute1,
     152             :                                                       multiLine: false,
     153           0 :                                                       onChanged: (profileAttribute1) {
     154           0 :                                                         String onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
     155           0 :                                                         Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-1", profileAttribute1);
     156           0 :                                                         Provider.of<ProfileInfoState>(context, listen: false).attributes[0] = profileAttribute1;
     157             :                                                       },
     158           0 :                                                       hintText: Provider.of<ProfileInfoState>(context).attributes[0] ?? AppLocalizations.of(context)!.profileInfoHint)),
     159           0 :                                               Padding(
     160           0 :                                                   padding: EdgeInsets.all(5.0),
     161           0 :                                                   child: CwtchTextField(
     162           0 :                                                       controller: ctrlrAttribute2,
     163             :                                                       multiLine: false,
     164           0 :                                                       onChanged: (profileAttribute2) {
     165           0 :                                                         String onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
     166           0 :                                                         Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-2", profileAttribute2);
     167           0 :                                                         Provider.of<ProfileInfoState>(context, listen: false).attributes[1] = profileAttribute2;
     168             :                                                       },
     169           0 :                                                       hintText: Provider.of<ProfileInfoState>(context).attributes[1] ?? AppLocalizations.of(context)!.profileInfoHint2)),
     170           0 :                                               Padding(
     171           0 :                                                   padding: EdgeInsets.all(5.0),
     172           0 :                                                   child: CwtchTextField(
     173           0 :                                                       controller: ctrlrAttribute3,
     174             :                                                       multiLine: false,
     175           0 :                                                       onChanged: (profileAttribute3) {
     176           0 :                                                         String onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
     177           0 :                                                         Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-3", profileAttribute3);
     178           0 :                                                         Provider.of<ProfileInfoState>(context, listen: false).attributes[2] = profileAttribute3;
     179             :                                                       },
     180           0 :                                                       hintText: Provider.of<ProfileInfoState>(context).attributes[2] ?? AppLocalizations.of(context)!.profileInfoHint3)),
     181             :                                             ],
     182             :                                           ))
     183             :                                     ],
     184             :                                   )),
     185           0 :                               Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
     186           0 :                                 SizedBox(
     187             :                                   height: 20,
     188             :                                 ),
     189           0 :                                 CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
     190           0 :                                 SizedBox(
     191             :                                   height: 20,
     192             :                                 ),
     193           0 :                                 CwtchTextField(
     194           0 :                                   key: Key("displayNameFormElement"),
     195           0 :                                   controller: ctrlrNick,
     196             :                                   autofocus: false,
     197           0 :                                   hintText: AppLocalizations.of(context)!.yourDisplayName,
     198           0 :                                   validator: (value) {
     199           0 :                                     if (value.isEmpty) {
     200           0 :                                       return AppLocalizations.of(context)!.displayNameTooltip;
     201             :                                     }
     202             :                                     return null;
     203             :                                   },
     204             :                                 ),
     205           0 :                                 SizedBox(
     206             :                                   height: 20,
     207             :                                 ),
     208           0 :                                 CwtchLabel(label: AppLocalizations.of(context)!.privateNameLabel),
     209           0 :                                 SizedBox(
     210             :                                   height: 20,
     211             :                                 ),
     212           0 :                                 CwtchTextField(
     213           0 :                                   key: Key("privateNameFormElement"),
     214           0 :                                   controller: ctrlrPrivateName,
     215             :                                   autofocus: false,
     216           0 :                                   hintText: AppLocalizations.of(context)!.privateNameHint,
     217             :                                 ),
     218             :                               ]),
     219           0 :                               Visibility(
     220           0 :                                   visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty,
     221           0 :                                   child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
     222           0 :                                     SizedBox(
     223             :                                       height: 20,
     224             :                                     ),
     225           0 :                                     CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
     226           0 :                                     SizedBox(
     227             :                                       height: 20,
     228             :                                     ),
     229           0 :                                     CwtchButtonTextField(
     230           0 :                                       controller: ctrlrOnion,
     231           0 :                                       onPressed: _copyOnion,
     232             :                                       readonly: true,
     233           0 :                                       icon: Icon(
     234             :                                         CwtchIcons.address_copy,
     235             :                                         size: 32,
     236             :                                       ),
     237           0 :                                       tooltip: AppLocalizations.of(context)!.copyBtn,
     238             :                                     )
     239             :                                   ])),
     240             :                               // We only allow setting password types on profile creation
     241             : 
     242             :                               // Enabled
     243           0 :                               Visibility(
     244             :                                   // FIXME don't show the disable switch in test mode...this is a bug relating to scrolling things into view
     245           0 :                                   visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty && (EnvironmentConfig.TEST_MODE == false),
     246           0 :                                   child: SwitchListTile(
     247           0 :                                     title: Text(AppLocalizations.of(context)!.profileEnabled, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor)),
     248           0 :                                     subtitle: Text(AppLocalizations.of(context)!.profileEnabledDescription),
     249           0 :                                     value: Provider.of<ProfileInfoState>(context).enabled,
     250           0 :                                     onChanged: (bool value) {
     251           0 :                                       Provider.of<ProfileInfoState>(context, listen: false).enabled = value;
     252             :                                       if (value) {
     253           0 :                                         if (Provider.of<ProfileInfoState>(context, listen: false).appearOffline == false) {
     254           0 :                                           Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(Provider.of<ProfileInfoState>(context, listen: false).onion, true, true, true);
     255             :                                         } else {
     256           0 :                                           Provider.of<FlwtchState>(context, listen: false).cwtch.ConfigureConnections(Provider.of<ProfileInfoState>(context, listen: false).onion, false, false, false);
     257             :                                         }
     258             :                                       } else {
     259           0 :                                         Provider.of<ProfileInfoState>(context, listen: false).deactivatePeerEngine(context);
     260             :                                       }
     261             :                                     },
     262           0 :                                     activeTrackColor: Provider.of<Settings>(context).theme.defaultButtonColor,
     263           0 :                                     inactiveTrackColor: Provider.of<Settings>(context).theme.defaultButtonDisabledColor,
     264           0 :                                     secondary: Icon(CwtchIcons.negative_heart_24px, color: Provider.of<Settings>(context).current().mainTextColor),
     265             :                                   )),
     266             : 
     267             :                               // Auto start
     268           0 :                               SwitchListTile(
     269           0 :                                 title: Text(AppLocalizations.of(context)!.profileAutostartLabel, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor)),
     270           0 :                                 subtitle: Text(AppLocalizations.of(context)!.profileAutostartDescription),
     271           0 :                                 value: Provider.of<ProfileInfoState>(context).autostart,
     272           0 :                                 onChanged: (bool value) {
     273           0 :                                   Provider.of<ProfileInfoState>(context, listen: false).autostart = value;
     274             : 
     275           0 :                                   if (Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty) {
     276           0 :                                     Provider.of<FlwtchState>(context, listen: false)
     277           0 :                                         .cwtch
     278           0 :                                         .SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.autostart", value ? "true" : "false");
     279             :                                   }
     280             :                                 },
     281           0 :                                 activeTrackColor: Provider.of<Settings>(context).theme.defaultButtonColor,
     282           0 :                                 inactiveTrackColor: Provider.of<Settings>(context).theme.defaultButtonDisabledColor,
     283           0 :                                 secondary: Icon(CwtchIcons.favorite_24dp, color: Provider.of<Settings>(context).current().mainTextColor),
     284             :                               ),
     285             : 
     286             :                               // Appear Offline
     287           0 :                               Visibility(
     288             :                                   // FIXME don't show the disable switch in test mode...this is a bug relating to scrolling things into view
     289           0 :                                   visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty && (EnvironmentConfig.TEST_MODE == false),
     290           0 :                                   child: SwitchListTile(
     291           0 :                                     title: Text(AppLocalizations.of(context)!.profileOfflineAtStart, style: TextStyle(color: Provider.of<Settings>(context).current().mainTextColor)),
     292           0 :                                     subtitle: Text(AppLocalizations.of(context)!.profileAppearOfflineDescription),
     293           0 :                                     value: Provider.of<ProfileInfoState>(context).appearOfflineAtStartup,
     294           0 :                                     onChanged: (bool value) {
     295           0 :                                       Provider.of<ProfileInfoState>(context, listen: false).appearOfflineAtStartup = value;
     296           0 :                                       var onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
     297           0 :                                       if (onion.isNotEmpty) {
     298           0 :                                         Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.appear-offline", value ? "true" : "false");
     299             :                                       }
     300             :                                     },
     301           0 :                                     activeTrackColor: Provider.of<Settings>(context).theme.defaultButtonColor,
     302           0 :                                     inactiveTrackColor: Provider.of<Settings>(context).theme.defaultButtonDisabledColor,
     303           0 :                                     secondary: Icon(CwtchIcons.favorite_24dp, color: Provider.of<Settings>(context).current().mainTextColor),
     304             :                                   )),
     305             : 
     306           0 :                               Visibility(
     307           0 :                                   visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
     308           0 :                                   child: SizedBox(
     309             :                                     height: 20,
     310             :                                   )),
     311           0 :                               Visibility(
     312           0 :                                   visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
     313           0 :                                   child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
     314           0 :                                     Checkbox(
     315           0 :                                       key: Key("passwordCheckBox"),
     316           0 :                                       value: usePassword,
     317           0 :                                       fillColor: MaterialStateProperty.all(theme.current().defaultButtonColor),
     318           0 :                                       activeColor: theme.current().defaultButtonActiveColor,
     319           0 :                                       onChanged: _handleSwitchPassword,
     320             :                                     ),
     321           0 :                                     Text(
     322           0 :                                       AppLocalizations.of(context)!.radioUsePassword,
     323           0 :                                       style: TextStyle(color: theme.current().mainTextColor),
     324             :                                     ),
     325           0 :                                     SizedBox(
     326             :                                       height: 20,
     327             :                                     ),
     328           0 :                                     Padding(
     329           0 :                                         padding: EdgeInsets.symmetric(horizontal: 24),
     330           0 :                                         child: Text(
     331           0 :                                           usePassword ? AppLocalizations.of(context)!.encryptedProfileDescription : AppLocalizations.of(context)!.plainProfileDescription,
     332             :                                           textAlign: TextAlign.center,
     333             :                                         ))
     334             :                                   ])),
     335           0 :                               SizedBox(
     336             :                                 height: 20,
     337             :                               ),
     338           0 :                               Visibility(
     339           0 :                                 visible: usePassword,
     340           0 :                                 child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
     341           0 :                                   Visibility(
     342           0 :                                       visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty && Provider.of<ProfileInfoState>(context).isEncrypted,
     343           0 :                                       child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
     344           0 :                                         CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
     345           0 :                                         SizedBox(
     346             :                                           height: 20,
     347             :                                         ),
     348           0 :                                         CwtchPasswordField(
     349           0 :                                           key: Key("currentPasswordFormElement"),
     350           0 :                                           controller: ctrlrOldPass,
     351           0 :                                           autoFillHints: [AutofillHints.newPassword],
     352           0 :                                           validator: (value) {
     353             :                                             // Password field can be empty when just updating the profile, not on creation
     354           0 :                                             if (Provider.of<ProfileInfoState>(context, listen: false).isEncrypted &&
     355           0 :                                                 Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty &&
     356           0 :                                                 value.isEmpty &&
     357           0 :                                                 usePassword) {
     358           0 :                                               return AppLocalizations.of(context)!.passwordErrorEmpty;
     359             :                                             }
     360           0 :                                             if (Provider.of<ErrorHandler>(context, listen: false).deleteProfileError == true) {
     361           0 :                                               return AppLocalizations.of(context)!.enterCurrentPasswordForDelete;
     362             :                                             }
     363             :                                             return null;
     364             :                                           },
     365             :                                         ),
     366           0 :                                         SizedBox(
     367             :                                           height: 20,
     368             :                                         ),
     369             :                                       ])),
     370           0 :                                   CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
     371           0 :                                   SizedBox(
     372             :                                     height: 20,
     373             :                                   ),
     374           0 :                                   CwtchPasswordField(
     375           0 :                                     key: Key("passwordFormElement"),
     376           0 :                                     controller: ctrlrPass,
     377           0 :                                     validator: (value) {
     378             :                                       // Password field can be empty when just updating the profile, not on creation
     379           0 :                                       if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) {
     380           0 :                                         return AppLocalizations.of(context)!.passwordErrorEmpty;
     381             :                                       }
     382           0 :                                       if (value != ctrlrPass2.value.text) {
     383           0 :                                         return AppLocalizations.of(context)!.passwordErrorMatch;
     384             :                                       }
     385             :                                       return null;
     386             :                                     },
     387             :                                   ),
     388           0 :                                   SizedBox(
     389             :                                     height: 20,
     390             :                                   ),
     391           0 :                                   CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
     392           0 :                                   SizedBox(
     393             :                                     height: 20,
     394             :                                   ),
     395           0 :                                   CwtchPasswordField(
     396           0 :                                       key: Key("confirmPasswordFormElement"),
     397           0 :                                       controller: ctrlrPass2,
     398           0 :                                       validator: (value) {
     399             :                                         // Password field can be empty when just updating the profile, not on creation
     400           0 :                                         if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) {
     401           0 :                                           return AppLocalizations.of(context)!.passwordErrorEmpty;
     402             :                                         }
     403           0 :                                         if (value != ctrlrPass.value.text) {
     404           0 :                                           return AppLocalizations.of(context)!.passwordErrorMatch;
     405             :                                         }
     406             :                                         return null;
     407             :                                       }),
     408             :                                 ]),
     409             :                               ),
     410           0 :                               SizedBox(
     411             :                                 height: 20,
     412             :                               ),
     413           0 :                               ElevatedButton(
     414           0 :                                 key: Key("createOrSaveProfileBtn"),
     415           0 :                                 onPressed: _createPressed,
     416           0 :                                 style: ElevatedButton.styleFrom(
     417           0 :                                   minimumSize: Size(400, 75),
     418           0 :                                   maximumSize: Size(800, 75),
     419           0 :                                   shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     420             :                                 ),
     421           0 :                                 child: Text(
     422           0 :                                   Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context)!.addNewProfileBtn : AppLocalizations.of(context)!.saveProfileBtn,
     423             :                                   textAlign: TextAlign.center,
     424             :                                 ),
     425             :                               ),
     426           0 :                               SizedBox(
     427             :                                 height: 20,
     428             :                               ),
     429           0 :                               Visibility(
     430           0 :                                   visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
     431           0 :                                   child: Tooltip(
     432           0 :                                       message: AppLocalizations.of(context)!.exportProfileTooltip,
     433           0 :                                       child: OutlinedButton.icon(
     434           0 :                                         style: OutlinedButton.styleFrom(
     435           0 :                                           minimumSize: Size(400, 75),
     436           0 :                                           maximumSize: Size(800, 75),
     437             :                                         ),
     438           0 :                                         onPressed: () {
     439           0 :                                           if (Platform.isAndroid) {
     440           0 :                                             Provider.of<FlwtchState>(context, listen: false).cwtch.ExportProfile(ctrlrOnion.value.text, ctrlrOnion.value.text + ".tar.gz");
     441           0 :                                             final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.fileSavedTo + " " + ctrlrOnion.value.text + ".tar.gz"));
     442           0 :                                             ScaffoldMessenger.of(context).showSnackBar(snackBar);
     443             :                                           } else {
     444           0 :                                             showCreateFilePicker(context).then((name) {
     445             :                                               if (name != null) {
     446           0 :                                                 Provider.of<FlwtchState>(context, listen: false).cwtch.ExportProfile(ctrlrOnion.value.text, name);
     447           0 :                                                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.fileSavedTo + " " + name));
     448           0 :                                                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     449             :                                               }
     450             :                                             });
     451             :                                           }
     452             :                                         },
     453           0 :                                         icon: Icon(Icons.import_export),
     454           0 :                                         label: Text(AppLocalizations.of(context)!.exportProfile),
     455             :                                       ))),
     456           0 :                               SizedBox(
     457             :                                 height: 20,
     458             :                               ),
     459           0 :                               Visibility(
     460           0 :                                   visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
     461           0 :                                   child: Tooltip(
     462           0 :                                       message: AppLocalizations.of(context)!.enterCurrentPasswordForDelete,
     463           0 :                                       child: FilledButton.icon(
     464           0 :                                         style: FilledButton.styleFrom(
     465           0 :                                           minimumSize: Size(400, 75),
     466           0 :                                           maximumSize: Size(800, 75),
     467           0 :                                           shape: RoundedRectangleBorder(
     468           0 :                                               side: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, width: 2.0),
     469           0 :                                               borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
     470             :                                         ),
     471           0 :                                         onPressed: () {
     472           0 :                                           showAlertDialog(context);
     473             :                                         },
     474           0 :                                         icon: Icon(Icons.delete_forever),
     475           0 :                                         label: Text(AppLocalizations.of(context)!.deleteBtn),
     476             :                                       )))
     477             :                             ]))))));
     478             :       });
     479             :     });
     480             :   }
     481             : 
     482           0 :   void _copyOnion() {
     483           0 :     Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
     484           0 :     final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
     485           0 :     ScaffoldMessenger.of(context).showSnackBar(snackBar);
     486             :   }
     487             : 
     488           0 :   void _createPressed() async {
     489             :     // This will run all the validations in the form including
     490             :     // checking that display name is not empty, and an actual check that the passwords
     491             :     // match (and are provided if the user has requested an encrypted profile).
     492           0 :     if (_formKey.currentState!.validate()) {
     493           0 :       if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty) {
     494           0 :         if (usePassword == true) {
     495           0 :           Provider.of<FlwtchState>(context, listen: false)
     496           0 :               .cwtch
     497           0 :               .CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text, Provider.of<ProfileInfoState>(context, listen: false).autostart)
     498           0 :               .then((profile_id) {
     499           0 :             Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(profile_id, "profile.private-name", ctrlrPrivateName.value.text);
     500             :           });
     501             :         } else {
     502           0 :           Provider.of<FlwtchState>(context, listen: false)
     503           0 :               .cwtch
     504           0 :               .CreateProfile(ctrlrNick.value.text, DefaultPassword, Provider.of<ProfileInfoState>(context, listen: false).autostart)
     505           0 :               .then((profile_id) {
     506           0 :             Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(profile_id, "profile.private-name", ctrlrPrivateName.value.text);
     507             :           });
     508             :         }
     509           0 :         Navigator.of(context).pop();
     510             :       } else {
     511             :         // Profile Editing
     512           0 :         var profile = Provider.of<ProfileInfoState>(context, listen: false).onion;
     513             :         // update name
     514           0 :         Provider.of<ProfileInfoState>(context, listen: false).nickname = ctrlrNick.value.text;
     515           0 :         Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(profile, "profile.name", ctrlrNick.value.text);
     516           0 :         Provider.of<ProfileInfoState>(context, listen: false).setPrivateName(ctrlrPrivateName.value.text);
     517           0 :         Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(profile, "profile.private-name", ctrlrPrivateName.value.text);
     518             : 
     519           0 :         if (ctrlrPass.value.text.isEmpty) {
     520           0 :           Navigator.of(context).pop();
     521             :         } else {
     522             :           // At this points passwords have been validated to be the same and not empty so update password
     523             :           // Use default password if the profile is unencrypted
     524           0 :           var password = Provider.of<ProfileInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.text : DefaultPassword;
     525           0 :           Provider.of<FlwtchState>(context, listen: false).cwtch.ChangePassword(profile, password, ctrlrPass.text, ctrlrPass2.text);
     526             : 
     527           0 :           EnvironmentConfig.debugLog("waiting for change password response");
     528           0 :           Future.delayed(const Duration(milliseconds: 500), () {
     529           0 :             if (globalErrorHandler.changePasswordError) {
     530             :               // TODO: This isn't ideal, but because onChange can be fired during this future check
     531             :               // and because the context can change after being popped we have this kind of double assertion...
     532             :               // There is probably a better pattern to handle this...
     533           0 :               if (AppLocalizations.of(context) != null) {
     534           0 :                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.passwordChangeError));
     535           0 :                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     536             :                 return;
     537             :               }
     538             :             }
     539           0 :           }).whenComplete(() {
     540           0 :             if (globalErrorHandler.explicitChangePasswordSuccess) {
     541             :               // we need to set the local encrypted status to display correct password forms on this run...
     542           0 :               Provider.of<ProfileInfoState>(context, listen: false).isEncrypted = true;
     543           0 :               final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.newPassword));
     544           0 :               ScaffoldMessenger.of(context).showSnackBar(snackBar);
     545           0 :               Navigator.pop(context);
     546             :               return; // otherwise round and round we go...
     547             :             }
     548             :           });
     549             :         }
     550             :       }
     551             :     }
     552             :   }
     553             : 
     554           0 :   showAlertDialog(BuildContext context) {
     555             :     // set up the buttons
     556           0 :     Widget cancelButton = ElevatedButton(
     557           0 :       child: Text(AppLocalizations.of(context)!.cancel),
     558           0 :       onPressed: () {
     559           0 :         Navigator.of(context).pop(); // dismiss dialog
     560             :       },
     561             :     );
     562           0 :     Widget continueButton = ElevatedButton(
     563           0 :         child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
     564           0 :         onPressed: () {
     565           0 :           var onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
     566           0 :           Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteProfile(onion, ctrlrOldPass.value.text);
     567             : 
     568           0 :           Future.delayed(
     569             :             const Duration(milliseconds: 500),
     570           0 :             () {
     571           0 :               if (globalErrorHandler.deleteProfileSuccess) {
     572           0 :                 final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteProfileSuccess + ":" + onion));
     573           0 :                 ScaffoldMessenger.of(context).showSnackBar(snackBar);
     574           0 :                 Navigator.of(context).popUntil((route) => route.isFirst); // dismiss dialog
     575             :               } else {
     576           0 :                 Navigator.of(context).pop();
     577             :               }
     578             :             },
     579             :           );
     580             :         });
     581             : 
     582             :     // set up the AlertDialog
     583           0 :     AlertDialog alert = AlertDialog(
     584           0 :       title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
     585           0 :       actions: [
     586             :         cancelButton,
     587             :         continueButton,
     588             :       ],
     589             :     );
     590             : 
     591             :     // show the dialog
     592           0 :     showDialog(
     593             :       context: context,
     594           0 :       builder: (BuildContext context) {
     595             :         return alert;
     596             :       },
     597             :     );
     598             :   }
     599             : }

Generated by: LCOV version 1.14