Browse Source

Merge pull request #864 from ente-io/manage_link_fixes

New TextInputDialog for enabling password lock on public link
Ashil 2 years ago
parent
commit
72bcfa989f

+ 3 - 0
lib/ui/components/dialog_widget.dart

@@ -172,6 +172,7 @@ class TextInputDialog extends StatefulWidget {
   final bool showOnlyLoadingState;
   final bool showOnlyLoadingState;
   final TextCapitalization? textCapitalization;
   final TextCapitalization? textCapitalization;
   final bool alwaysShowSuccessState;
   final bool alwaysShowSuccessState;
+  final bool isPasswordInput;
   const TextInputDialog({
   const TextInputDialog({
     required this.title,
     required this.title,
     this.body,
     this.body,
@@ -188,6 +189,7 @@ class TextInputDialog extends StatefulWidget {
     this.textCapitalization,
     this.textCapitalization,
     this.showOnlyLoadingState = false,
     this.showOnlyLoadingState = false,
     this.alwaysShowSuccessState = false,
     this.alwaysShowSuccessState = false,
+    this.isPasswordInput = false,
     super.key,
     super.key,
   });
   });
 
 
@@ -247,6 +249,7 @@ class _TextInputDialogState extends State<TextInputDialog> {
                 showOnlyLoadingState: widget.showOnlyLoadingState,
                 showOnlyLoadingState: widget.showOnlyLoadingState,
                 textCapitalization: widget.textCapitalization,
                 textCapitalization: widget.textCapitalization,
                 alwaysShowSuccessState: widget.alwaysShowSuccessState,
                 alwaysShowSuccessState: widget.alwaysShowSuccessState,
+                isPasswordInput: widget.isPasswordInput,
               ),
               ),
             ),
             ),
             const SizedBox(height: 36),
             const SizedBox(height: 36),

+ 31 - 1
lib/ui/components/text_input_widget.dart

@@ -26,6 +26,7 @@ class TextInputWidget extends StatefulWidget {
   final bool popNavAfterSubmission;
   final bool popNavAfterSubmission;
   final bool shouldSurfaceExecutionStates;
   final bool shouldSurfaceExecutionStates;
   final TextCapitalization? textCapitalization;
   final TextCapitalization? textCapitalization;
+  final bool isPasswordInput;
   const TextInputWidget({
   const TextInputWidget({
     required this.onSubmit,
     required this.onSubmit,
     this.label,
     this.label,
@@ -42,6 +43,7 @@ class TextInputWidget extends StatefulWidget {
     this.popNavAfterSubmission = false,
     this.popNavAfterSubmission = false,
     this.shouldSurfaceExecutionStates = true,
     this.shouldSurfaceExecutionStates = true,
     this.textCapitalization = TextCapitalization.none,
     this.textCapitalization = TextCapitalization.none,
+    this.isPasswordInput = false,
     super.key,
     super.key,
   });
   });
 
 
@@ -53,6 +55,7 @@ class _TextInputWidgetState extends State<TextInputWidget> {
   ExecutionState executionState = ExecutionState.idle;
   ExecutionState executionState = ExecutionState.idle;
   final _textController = TextEditingController();
   final _textController = TextEditingController();
   final _debouncer = Debouncer(const Duration(milliseconds: 300));
   final _debouncer = Debouncer(const Duration(milliseconds: 300));
+  late final ValueNotifier<bool> _obscureTextNotifier;
 
 
   ///This is to pass if the TextInputWidget is in a dialog and an error is
   ///This is to pass if the TextInputWidget is in a dialog and an error is
   ///thrown in executing onSubmit by passing it as arg in Navigator.pop()
   ///thrown in executing onSubmit by passing it as arg in Navigator.pop()
@@ -68,12 +71,15 @@ class _TextInputWidgetState extends State<TextInputWidget> {
         selection: TextSelection.collapsed(offset: widget.initialValue!.length),
         selection: TextSelection.collapsed(offset: widget.initialValue!.length),
       );
       );
     }
     }
+    _obscureTextNotifier = ValueNotifier(widget.isPasswordInput);
+    _obscureTextNotifier.addListener(_safeRefresh);
     super.initState();
     super.initState();
   }
   }
 
 
   @override
   @override
   void dispose() {
   void dispose() {
     widget.submitNotifier?.removeListener(_onSubmit);
     widget.submitNotifier?.removeListener(_onSubmit);
+    _obscureTextNotifier.dispose();
     _textController.dispose();
     _textController.dispose();
     super.dispose();
     super.dispose();
   }
   }
@@ -105,6 +111,7 @@ class _TextInputWidgetState extends State<TextInputWidget> {
             inputFormatters: widget.maxLength != null
             inputFormatters: widget.maxLength != null
                 ? [LengthLimitingTextInputFormatter(50)]
                 ? [LengthLimitingTextInputFormatter(50)]
                 : null,
                 : null,
+            obscureText: _obscureTextNotifier.value,
             decoration: InputDecoration(
             decoration: InputDecoration(
               hintText: widget.hintText,
               hintText: widget.hintText,
               hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted),
               hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted),
@@ -134,6 +141,7 @@ class _TextInputWidgetState extends State<TextInputWidget> {
                     executionState: executionState,
                     executionState: executionState,
                     shouldSurfaceExecutionStates:
                     shouldSurfaceExecutionStates:
                         widget.shouldSurfaceExecutionStates,
                         widget.shouldSurfaceExecutionStates,
+                    obscureTextNotifier: _obscureTextNotifier,
                   ),
                   ),
                 ),
                 ),
               ),
               ),
@@ -186,6 +194,12 @@ class _TextInputWidgetState extends State<TextInputWidget> {
     );
     );
   }
   }
 
 
+  void _safeRefresh() {
+    if (mounted) {
+      setState(() {});
+    }
+  }
+
   void _onSubmit() async {
   void _onSubmit() async {
     _debouncer.run(
     _debouncer.run(
       () => Future(() {
       () => Future(() {
@@ -279,9 +293,11 @@ class _TextInputWidgetState extends State<TextInputWidget> {
 class SuffixIconWidget extends StatelessWidget {
 class SuffixIconWidget extends StatelessWidget {
   final ExecutionState executionState;
   final ExecutionState executionState;
   final bool shouldSurfaceExecutionStates;
   final bool shouldSurfaceExecutionStates;
+  final ValueNotifier? obscureTextNotifier;
   const SuffixIconWidget({
   const SuffixIconWidget({
     required this.executionState,
     required this.executionState,
     required this.shouldSurfaceExecutionStates,
     required this.shouldSurfaceExecutionStates,
+    this.obscureTextNotifier,
     super.key,
     super.key,
   });
   });
 
 
@@ -291,7 +307,21 @@ class SuffixIconWidget extends StatelessWidget {
     final colorScheme = getEnteColorScheme(context);
     final colorScheme = getEnteColorScheme(context);
     if (executionState == ExecutionState.idle ||
     if (executionState == ExecutionState.idle ||
         !shouldSurfaceExecutionStates) {
         !shouldSurfaceExecutionStates) {
-      trailingWidget = const SizedBox.shrink();
+      if (obscureTextNotifier != null) {
+        trailingWidget = GestureDetector(
+          onTap: () {
+            obscureTextNotifier!.value = !obscureTextNotifier!.value;
+          },
+          child: Icon(
+            obscureTextNotifier!.value
+                ? Icons.visibility_off_outlined
+                : Icons.visibility,
+            color: obscureTextNotifier!.value ? colorScheme.strokeMuted : null,
+          ),
+        );
+      } else {
+        trailingWidget = const SizedBox.shrink();
+      }
     } else if (executionState == ExecutionState.inProgress) {
     } else if (executionState == ExecutionState.inProgress) {
       trailingWidget = EnteLoadingWidget(
       trailingWidget = EnteLoadingWidget(
         color: colorScheme.strokeMuted,
         color: colorScheme.strokeMuted,

+ 33 - 87
lib/ui/sharing/manage_links_widget.dart

@@ -81,8 +81,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                           context,
                           context,
                           {'enableCollect': value},
                           {'enableCollect': value},
                         );
                         );
-
-                        setState(() {});
                       },
                       },
                     ),
                     ),
                   ),
                   ),
@@ -170,7 +168,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                             "Viewers can still take screenshots or save a copy of your photos using external tools",
                             "Viewers can still take screenshots or save a copy of your photos using external tools",
                           );
                           );
                         }
                         }
-                        setState(() {});
                       },
                       },
                     ),
                     ),
                   ),
                   ),
@@ -190,23 +187,33 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                       value: isPasswordEnabled,
                       value: isPasswordEnabled,
                       onChanged: (enablePassword) async {
                       onChanged: (enablePassword) async {
                         if (enablePassword) {
                         if (enablePassword) {
-                          final inputResult =
-                              await _displayLinkPasswordInput(context);
-                          if (inputResult != null &&
-                              inputResult == 'ok' &&
-                              _textFieldController.text.trim().isNotEmpty) {
-                            final propToUpdate = await _getEncryptedPassword(
-                              _textFieldController.text,
-                            );
-                            await _updateUrlSettings(context, propToUpdate);
-                          }
+                          showTextInputDialog(
+                            context,
+                            title: "Set a password",
+                            submitButtonLabel: "Lock",
+                            hintText: "Enter password",
+                            isPasswordInput: true,
+                            alwaysShowSuccessState: true,
+                            onSubmit: (String password) async {
+                              if (password.trim().isNotEmpty) {
+                                final propToUpdate =
+                                    await _getEncryptedPassword(
+                                  password,
+                                );
+                                await _updateUrlSettings(
+                                  context,
+                                  propToUpdate,
+                                  showProgressDialog: false,
+                                );
+                              }
+                            },
+                          );
                         } else {
                         } else {
                           await _updateUrlSettings(
                           await _updateUrlSettings(
                             context,
                             context,
                             {'disablePassword': true},
                             {'disablePassword': true},
                           );
                           );
                         }
                         }
-                        setState(() {});
                       },
                       },
                     ),
                     ),
                   ),
                   ),
@@ -242,72 +249,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
     );
     );
   }
   }
 
 
-  final TextEditingController _textFieldController = TextEditingController();
-
-  Future<String?> _displayLinkPasswordInput(BuildContext context) async {
-    _textFieldController.clear();
-    return showDialog<String>(
-      context: context,
-      builder: (context) {
-        bool passwordVisible = false;
-        return StatefulBuilder(
-          builder: (context, setState) {
-            return AlertDialog(
-              title: const Text('Enter password'),
-              content: TextFormField(
-                autofillHints: const [AutofillHints.newPassword],
-                decoration: InputDecoration(
-                  hintText: "Password",
-                  contentPadding: const EdgeInsets.all(12),
-                  suffixIcon: IconButton(
-                    icon: Icon(
-                      passwordVisible ? Icons.visibility : Icons.visibility_off,
-                      color: Colors.white.withOpacity(0.5),
-                      size: 20,
-                    ),
-                    onPressed: () {
-                      passwordVisible = !passwordVisible;
-                      setState(() {});
-                    },
-                  ),
-                ),
-                obscureText: !passwordVisible,
-                controller: _textFieldController,
-                autofocus: true,
-                autocorrect: false,
-                keyboardType: TextInputType.visiblePassword,
-                onChanged: (_) {
-                  setState(() {});
-                },
-              ),
-              actions: <Widget>[
-                TextButton(
-                  child: Text(
-                    'Cancel',
-                    style: Theme.of(context).textTheme.subtitle2,
-                  ),
-                  onPressed: () {
-                    Navigator.pop(context, 'cancel');
-                  },
-                ),
-                TextButton(
-                  child:
-                      Text('Ok', style: Theme.of(context).textTheme.subtitle2),
-                  onPressed: () {
-                    if (_textFieldController.text.trim().isEmpty) {
-                      return;
-                    }
-                    Navigator.pop(context, 'ok');
-                  },
-                ),
-              ],
-            );
-          },
-        );
-      },
-    );
-  }
-
   Future<Map<String, dynamic>> _getEncryptedPassword(String pass) async {
   Future<Map<String, dynamic>> _getEncryptedPassword(String pass) async {
     assert(
     assert(
       Sodium.cryptoPwhashAlgArgon2id13 == Sodium.cryptoPwhashAlgDefault,
       Sodium.cryptoPwhashAlgArgon2id13 == Sodium.cryptoPwhashAlgDefault,
@@ -331,19 +272,24 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
   }
   }
 
 
   Future<void> _updateUrlSettings(
   Future<void> _updateUrlSettings(
-    BuildContext context,
-    Map<String, dynamic> prop,
-  ) async {
-    final dialog = createProgressDialog(context, "Please wait...");
-    await dialog.show();
+      BuildContext context, Map<String, dynamic> prop,
+      {bool showProgressDialog = true}) async {
+    final dialog = showProgressDialog
+        ? createProgressDialog(context, "Please wait...")
+        : null;
+    await dialog?.show();
     try {
     try {
       await CollectionsService.instance
       await CollectionsService.instance
           .updateShareUrl(widget.collection!, prop);
           .updateShareUrl(widget.collection!, prop);
-      await dialog.hide();
+      await dialog?.hide();
       showShortToast(context, "Album updated");
       showShortToast(context, "Album updated");
+      if (mounted) {
+        setState(() {});
+      }
     } catch (e) {
     } catch (e) {
-      await dialog.hide();
+      await dialog?.hide();
       await showGenericErrorDialog(context: context);
       await showGenericErrorDialog(context: context);
+      rethrow;
     }
     }
   }
   }
 }
 }

+ 2 - 0
lib/utils/dialog_util.dart

@@ -272,6 +272,7 @@ Future<dynamic> showTextInputDialog(
   bool showOnlyLoadingState = false,
   bool showOnlyLoadingState = false,
   TextCapitalization textCapitalization = TextCapitalization.none,
   TextCapitalization textCapitalization = TextCapitalization.none,
   bool alwaysShowSuccessState = false,
   bool alwaysShowSuccessState = false,
+  bool isPasswordInput = false,
 }) {
 }) {
   return showDialog(
   return showDialog(
     barrierColor: backdropFaintDark,
     barrierColor: backdropFaintDark,
@@ -298,6 +299,7 @@ Future<dynamic> showTextInputDialog(
             showOnlyLoadingState: showOnlyLoadingState,
             showOnlyLoadingState: showOnlyLoadingState,
             textCapitalization: textCapitalization,
             textCapitalization: textCapitalization,
             alwaysShowSuccessState: alwaysShowSuccessState,
             alwaysShowSuccessState: alwaysShowSuccessState,
+            isPasswordInput: isPasswordInput,
           ),
           ),
         ),
         ),
       );
       );