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