Merge pull request #864 from ente-io/manage_link_fixes
New TextInputDialog for enabling password lock on public link
This commit is contained in:
commit
72bcfa989f
4 changed files with 69 additions and 88 deletions
|
@ -172,6 +172,7 @@ class TextInputDialog extends StatefulWidget {
|
|||
final bool showOnlyLoadingState;
|
||||
final TextCapitalization? textCapitalization;
|
||||
final bool alwaysShowSuccessState;
|
||||
final bool isPasswordInput;
|
||||
const TextInputDialog({
|
||||
required this.title,
|
||||
this.body,
|
||||
|
@ -188,6 +189,7 @@ class TextInputDialog extends StatefulWidget {
|
|||
this.textCapitalization,
|
||||
this.showOnlyLoadingState = false,
|
||||
this.alwaysShowSuccessState = false,
|
||||
this.isPasswordInput = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -247,6 +249,7 @@ class _TextInputDialogState extends State<TextInputDialog> {
|
|||
showOnlyLoadingState: widget.showOnlyLoadingState,
|
||||
textCapitalization: widget.textCapitalization,
|
||||
alwaysShowSuccessState: widget.alwaysShowSuccessState,
|
||||
isPasswordInput: widget.isPasswordInput,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
|
|
|
@ -26,6 +26,7 @@ class TextInputWidget extends StatefulWidget {
|
|||
final bool popNavAfterSubmission;
|
||||
final bool shouldSurfaceExecutionStates;
|
||||
final TextCapitalization? textCapitalization;
|
||||
final bool isPasswordInput;
|
||||
const TextInputWidget({
|
||||
required this.onSubmit,
|
||||
this.label,
|
||||
|
@ -42,6 +43,7 @@ class TextInputWidget extends StatefulWidget {
|
|||
this.popNavAfterSubmission = false,
|
||||
this.shouldSurfaceExecutionStates = true,
|
||||
this.textCapitalization = TextCapitalization.none,
|
||||
this.isPasswordInput = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -53,6 +55,7 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
ExecutionState executionState = ExecutionState.idle;
|
||||
final _textController = TextEditingController();
|
||||
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
|
||||
///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),
|
||||
);
|
||||
}
|
||||
_obscureTextNotifier = ValueNotifier(widget.isPasswordInput);
|
||||
_obscureTextNotifier.addListener(_safeRefresh);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.submitNotifier?.removeListener(_onSubmit);
|
||||
_obscureTextNotifier.dispose();
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -105,6 +111,7 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
inputFormatters: widget.maxLength != null
|
||||
? [LengthLimitingTextInputFormatter(50)]
|
||||
: null,
|
||||
obscureText: _obscureTextNotifier.value,
|
||||
decoration: InputDecoration(
|
||||
hintText: widget.hintText,
|
||||
hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted),
|
||||
|
@ -134,6 +141,7 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
executionState: executionState,
|
||||
shouldSurfaceExecutionStates:
|
||||
widget.shouldSurfaceExecutionStates,
|
||||
obscureTextNotifier: _obscureTextNotifier,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -186,6 +194,12 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
void _safeRefresh() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSubmit() async {
|
||||
_debouncer.run(
|
||||
() => Future(() {
|
||||
|
@ -279,9 +293,11 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
|||
class SuffixIconWidget extends StatelessWidget {
|
||||
final ExecutionState executionState;
|
||||
final bool shouldSurfaceExecutionStates;
|
||||
final ValueNotifier? obscureTextNotifier;
|
||||
const SuffixIconWidget({
|
||||
required this.executionState,
|
||||
required this.shouldSurfaceExecutionStates,
|
||||
this.obscureTextNotifier,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -291,7 +307,21 @@ class SuffixIconWidget extends StatelessWidget {
|
|||
final colorScheme = getEnteColorScheme(context);
|
||||
if (executionState == ExecutionState.idle ||
|
||||
!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) {
|
||||
trailingWidget = EnteLoadingWidget(
|
||||
color: colorScheme.strokeMuted,
|
||||
|
|
|
@ -81,8 +81,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
|
|||
context,
|
||||
{'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",
|
||||
);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -190,23 +187,33 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
|
|||
value: isPasswordEnabled,
|
||||
onChanged: (enablePassword) async {
|
||||
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 {
|
||||
await _updateUrlSettings(
|
||||
context,
|
||||
{'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 {
|
||||
assert(
|
||||
Sodium.cryptoPwhashAlgArgon2id13 == Sodium.cryptoPwhashAlgDefault,
|
||||
|
@ -331,19 +272,24 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
|
|||
}
|
||||
|
||||
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 {
|
||||
await CollectionsService.instance
|
||||
.updateShareUrl(widget.collection!, prop);
|
||||
await dialog.hide();
|
||||
await dialog?.hide();
|
||||
showShortToast(context, "Album updated");
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
await dialog?.hide();
|
||||
await showGenericErrorDialog(context: context);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -272,6 +272,7 @@ Future<dynamic> showTextInputDialog(
|
|||
bool showOnlyLoadingState = false,
|
||||
TextCapitalization textCapitalization = TextCapitalization.none,
|
||||
bool alwaysShowSuccessState = false,
|
||||
bool isPasswordInput = false,
|
||||
}) {
|
||||
return showDialog(
|
||||
barrierColor: backdropFaintDark,
|
||||
|
@ -298,6 +299,7 @@ Future<dynamic> showTextInputDialog(
|
|||
showOnlyLoadingState: showOnlyLoadingState,
|
||||
textCapitalization: textCapitalization,
|
||||
alwaysShowSuccessState: alwaysShowSuccessState,
|
||||
isPasswordInput: isPasswordInput,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue