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 : }
|