From ba1d7bf2b016f01a75d40c6371eedd28b9a46d75 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 08:44:11 +0530 Subject: [PATCH 01/29] Made a non functional version of TextInputWidget --- lib/ui/components/dialog_widget.dart | 123 ++++++++++++++++++ .../gallery/gallery_app_bar_widget.dart | 65 +++++---- lib/utils/dialog_util.dart | 26 ++++ 3 files changed, 190 insertions(+), 24 deletions(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index df26b12da..391936ee8 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -151,3 +151,126 @@ class Actions extends StatelessWidget { ); } } + +class TextInputDialog extends StatelessWidget { + final String title; + final String? body; + final List buttons; + final IconData? icon; + final String? label; + final String? message; + const TextInputDialog({ + required this.title, + this.body, + required this.buttons, + this.icon, + this.label, + this.message, + super.key, + }); + + @override + Widget build(BuildContext context) { + final widthOfScreen = MediaQuery.of(context).size.width; + final isMobileSmall = widthOfScreen <= mobileSmallThreshold; + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + var textInputChildren = []; + if (label != null) textInputChildren.add(Text(label!)); + textInputChildren.add( + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Material( + child: TextFormField( + decoration: InputDecoration( + hintText: "Placeholder", + hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), + filled: true, + contentPadding: const EdgeInsets.symmetric( + vertical: 11, + horizontal: 11, + ), + border: const UnderlineInputBorder( + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: colorScheme.strokeMuted), + borderRadius: BorderRadius.circular(8), + ), + prefixIconConstraints: const BoxConstraints( + maxHeight: 44, + maxWidth: 44, + minHeight: 44, + minWidth: 44, + ), + suffixIconConstraints: const BoxConstraints( + maxHeight: 44, + maxWidth: 44, + minHeight: 44, + minWidth: 44, + ), + prefixIcon: Icon( + Icons.search_outlined, + color: colorScheme.strokeMuted, + ), + ), + ), + ), + ), + ); + if (message != null) { + textInputChildren.add( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + message!, + style: textTheme.small.copyWith(color: colorScheme.textMuted), + ), + ), + ); + } + textInputChildren = + addSeparators(textInputChildren, const SizedBox(height: 4)); + return Container( + width: min(widthOfScreen, 320), + padding: isMobileSmall + ? const EdgeInsets.all(0) + : const EdgeInsets.fromLTRB(6, 8, 6, 6), + decoration: BoxDecoration( + color: colorScheme.backgroundElevated, + boxShadow: shadowFloatLight, + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ContentContainer( + title: title, + body: body, + icon: icon, + ), + Padding( + padding: const EdgeInsets.only(top: 19), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: textInputChildren, + ), + ), + const SizedBox(height: 36), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded(child: buttons.first), + const SizedBox(width: 8), + Expanded(child: buttons.last), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 8d723222f..4ddb54cd8 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -17,7 +17,7 @@ import 'package:photos/services/collections_service.dart'; import 'package:photos/services/sync_service.dart'; import 'package:photos/services/update_service.dart'; import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; -import 'package:photos/ui/common/rename_dialog.dart'; +// import 'package:photos/ui/common/rename_dialog.dart'; import 'package:photos/ui/components/action_sheet_widget.dart'; import 'package:photos/ui/components/button_widget.dart'; import 'package:photos/ui/components/dialog_widget.dart'; @@ -111,31 +111,48 @@ class _GalleryAppBarWidgetState extends State { if (widget.type != GalleryType.ownedCollection) { return; } - final result = await showDialog( - context: context, - builder: (BuildContext context) { - return RenameDialog(_appBarTitle, 'Album'); - }, - barrierColor: Colors.black.withOpacity(0.85), + showTextInputDialog( + context, + title: "Title", + message: "Message", + buttons: const [ + ButtonWidget( + buttonType: ButtonType.secondary, + buttonSize: ButtonSize.small, + labelText: "Cancel", + ), + ButtonWidget( + buttonSize: ButtonSize.small, + buttonType: ButtonType.neutral, + labelText: "Rename", + ), + ], ); - // indicates user cancelled the rename request - if (result == null || result.trim() == _appBarTitle!.trim()) { - return; - } + // final result = await showDialog( + // context: context, + // builder: (BuildContext context) { + // return RenameDialog(_appBarTitle, 'Album'); + // }, + // barrierColor: Colors.black.withOpacity(0.85), + // ); + // // indicates user cancelled the rename request + // if (result == null || result.trim() == _appBarTitle!.trim()) { + // return; + // } - final dialog = createProgressDialog(context, "Changing name..."); - await dialog.show(); - try { - await CollectionsService.instance.rename(widget.collection!, result); - await dialog.hide(); - if (mounted) { - _appBarTitle = result; - setState(() {}); - } - } catch (e) { - await dialog.hide(); - showGenericErrorDialog(context: context); - } + // final dialog = createProgressDialog(context, "Changing name..."); + // await dialog.show(); + // try { + // await CollectionsService.instance.rename(widget.collection!, result); + // await dialog.hide(); + // if (mounted) { + // _appBarTitle = result; + // setState(() {}); + // } + // } catch (e) { + // await dialog.hide(); + // showGenericErrorDialog(context: context); + // } } Future _leaveAlbum(BuildContext context) async { diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index d94764de6..b8dcb9c8c 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -250,3 +250,29 @@ Future showConfettiDialog({ routeSettings: routeSettings, ); } + +showTextInputDialog( + BuildContext context, { + required String title, + String? body, + required List buttons, + IconData? icon, + String? label, + String? message, +}) { + return showDialog( + context: context, + builder: (context) { + return Center( + child: TextInputDialog( + title: title, + message: message, + label: label, + body: body, + icon: icon, + buttons: buttons, + ), + ); + }, + ); +} From c48108a2dade0469f8df11d40f4107c6252ff87b Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 10:43:22 +0530 Subject: [PATCH 02/29] Made a working TextInputDialog --- lib/ui/components/dialog_widget.dart | 53 +++++++++++++++---- .../gallery/gallery_app_bar_widget.dart | 29 +++++----- lib/utils/dialog_util.dart | 6 ++- 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index 391936ee8..06e43d666 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -6,8 +6,11 @@ import 'package:photos/theme/colors.dart'; import 'package:photos/theme/effects.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/components/button_widget.dart'; +import 'package:photos/ui/components/models/button_type.dart'; import 'package:photos/utils/separators_util.dart'; +typedef FutureVoidCallbackParamStr = Future Function(String); + ///Will return null if dismissed by tapping outside Future showDialogWidget({ required BuildContext context, @@ -152,23 +155,31 @@ class Actions extends StatelessWidget { } } -class TextInputDialog extends StatelessWidget { +class TextInputDialog extends StatefulWidget { final String title; final String? body; - final List buttons; + final String confirmationButtonLabel; final IconData? icon; final String? label; final String? message; + final FutureVoidCallbackParamStr onConfirm; const TextInputDialog({ required this.title, this.body, - required this.buttons, + required this.confirmationButtonLabel, + required this.onConfirm, this.icon, this.label, this.message, super.key, }); + @override + State createState() => _TextInputDialogState(); +} + +class _TextInputDialogState extends State { + final TextEditingController _textController = TextEditingController(); @override Widget build(BuildContext context) { final widthOfScreen = MediaQuery.of(context).size.width; @@ -176,12 +187,13 @@ class TextInputDialog extends StatelessWidget { final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); var textInputChildren = []; - if (label != null) textInputChildren.add(Text(label!)); + if (widget.label != null) textInputChildren.add(Text(widget.label!)); textInputChildren.add( ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(8)), child: Material( child: TextFormField( + controller: _textController, decoration: InputDecoration( hintText: "Placeholder", hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), @@ -218,12 +230,12 @@ class TextInputDialog extends StatelessWidget { ), ), ); - if (message != null) { + if (widget.message != null) { textInputChildren.add( Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( - message!, + widget.message!, style: textTheme.small.copyWith(color: colorScheme.textMuted), ), ), @@ -247,9 +259,9 @@ class TextInputDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ ContentContainer( - title: title, - body: body, - icon: icon, + title: widget.title, + body: widget.body, + icon: widget.icon, ), Padding( padding: const EdgeInsets.only(top: 19), @@ -263,9 +275,24 @@ class TextInputDialog extends StatelessWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - Expanded(child: buttons.first), + const Expanded( + child: ButtonWidget( + buttonType: ButtonType.secondary, + buttonSize: ButtonSize.small, + labelText: "Cancel", + isInAlert: true, + ), + ), const SizedBox(width: 8), - Expanded(child: buttons.last), + Expanded( + child: ButtonWidget( + buttonSize: ButtonSize.small, + buttonType: ButtonType.neutral, + labelText: widget.confirmationButtonLabel, + onTap: _onTap, + isInAlert: true, + ), + ), ], ) ], @@ -273,4 +300,8 @@ class TextInputDialog extends StatelessWidget { ), ); } + + Future _onTap() async { + await widget.onConfirm.call(_textController.text); + } } diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 4ddb54cd8..6868d6367 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -115,18 +115,23 @@ class _GalleryAppBarWidgetState extends State { context, title: "Title", message: "Message", - buttons: const [ - ButtonWidget( - buttonType: ButtonType.secondary, - buttonSize: ButtonSize.small, - labelText: "Cancel", - ), - ButtonWidget( - buttonSize: ButtonSize.small, - buttonType: ButtonType.neutral, - labelText: "Rename", - ), - ], + confirmationButtonLabel: "Rename", + onConfirm: (String text) async { + // indicates user cancelled the rename request + if (text == "" || text.trim() == _appBarTitle!.trim()) { + return; + } + + try { + await CollectionsService.instance.rename(widget.collection!, text); + if (mounted) { + _appBarTitle = text; + setState(() {}); + } + } catch (e) { + showGenericErrorDialog(context: context); + } + }, ); // final result = await showDialog( // context: context, diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index b8dcb9c8c..7d8bb2b4f 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -255,10 +255,11 @@ showTextInputDialog( BuildContext context, { required String title, String? body, - required List buttons, + required String confirmationButtonLabel, IconData? icon, String? label, String? message, + required FutureVoidCallbackParamStr onConfirm, }) { return showDialog( context: context, @@ -270,7 +271,8 @@ showTextInputDialog( label: label, body: body, icon: icon, - buttons: buttons, + confirmationButtonLabel: confirmationButtonLabel, + onConfirm: onConfirm, ), ); }, From da3088eab188dc25a7eb3a2ddb4f21a2ed8f4ef6 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 10:58:57 +0530 Subject: [PATCH 03/29] Used new TextInputDialog for renaming album --- .../gallery/gallery_app_bar_widget.dart | 35 ++++--------------- lib/utils/dialog_util.dart | 2 +- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 6868d6367..559cc50a5 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -111,10 +111,9 @@ class _GalleryAppBarWidgetState extends State { if (widget.type != GalleryType.ownedCollection) { return; } - showTextInputDialog( + final result = await showTextInputDialog( context, - title: "Title", - message: "Message", + title: "Rename album", confirmationButtonLabel: "Rename", onConfirm: (String text) async { // indicates user cancelled the rename request @@ -129,35 +128,13 @@ class _GalleryAppBarWidgetState extends State { setState(() {}); } } catch (e) { - showGenericErrorDialog(context: context); + rethrow; } }, ); - // final result = await showDialog( - // context: context, - // builder: (BuildContext context) { - // return RenameDialog(_appBarTitle, 'Album'); - // }, - // barrierColor: Colors.black.withOpacity(0.85), - // ); - // // indicates user cancelled the rename request - // if (result == null || result.trim() == _appBarTitle!.trim()) { - // return; - // } - - // final dialog = createProgressDialog(context, "Changing name..."); - // await dialog.show(); - // try { - // await CollectionsService.instance.rename(widget.collection!, result); - // await dialog.hide(); - // if (mounted) { - // _appBarTitle = result; - // setState(() {}); - // } - // } catch (e) { - // await dialog.hide(); - // showGenericErrorDialog(context: context); - // } + if (result == ButtonAction.error) { + showGenericErrorDialog(context: context); + } } Future _leaveAlbum(BuildContext context) async { diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 7d8bb2b4f..4a6ed2495 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -251,7 +251,7 @@ Future showConfettiDialog({ ); } -showTextInputDialog( +Future showTextInputDialog( BuildContext context, { required String title, String? body, From d5dc0a5858269b1816efdf928f093c26a679331b Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 11:16:49 +0530 Subject: [PATCH 04/29] Made hintText configurable in TextInputDialog --- lib/ui/components/dialog_widget.dart | 4 +++- lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 1 + lib/utils/dialog_util.dart | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index 06e43d666..727955e09 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -163,6 +163,7 @@ class TextInputDialog extends StatefulWidget { final String? label; final String? message; final FutureVoidCallbackParamStr onConfirm; + final String? hintText; const TextInputDialog({ required this.title, this.body, @@ -171,6 +172,7 @@ class TextInputDialog extends StatefulWidget { this.icon, this.label, this.message, + this.hintText, super.key, }); @@ -195,7 +197,7 @@ class _TextInputDialogState extends State { child: TextFormField( controller: _textController, decoration: InputDecoration( - hintText: "Placeholder", + hintText: widget.hintText, hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), filled: true, contentPadding: const EdgeInsets.symmetric( diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 559cc50a5..1f214c0e4 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -115,6 +115,7 @@ class _GalleryAppBarWidgetState extends State { context, title: "Rename album", confirmationButtonLabel: "Rename", + hintText: "Enter album name", onConfirm: (String text) async { // indicates user cancelled the rename request if (text == "" || text.trim() == _appBarTitle!.trim()) { diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 4a6ed2495..7699cdae8 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -259,6 +259,7 @@ Future showTextInputDialog( IconData? icon, String? label, String? message, + String? hintText, required FutureVoidCallbackParamStr onConfirm, }) { return showDialog( @@ -273,6 +274,7 @@ Future showTextInputDialog( icon: icon, confirmationButtonLabel: confirmationButtonLabel, onConfirm: onConfirm, + hintText: hintText, ), ); }, From 64b479dbf4a612d823dbdd076b7cc7a8a1a83283 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 12:40:03 +0530 Subject: [PATCH 05/29] Created TextInputWidget and used it in TextInputDialog --- lib/ui/components/dialog_widget.dart | 71 +++---------------- lib/ui/components/text_input_widget.dart | 89 ++++++++++++++++++++++++ lib/utils/dialog_util.dart | 2 + 3 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 lib/ui/components/text_input_widget.dart diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index 727955e09..e7e387d71 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -7,6 +7,7 @@ import 'package:photos/theme/effects.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/components/button_widget.dart'; import 'package:photos/ui/components/models/button_type.dart'; +import 'package:photos/ui/components/text_input_widget.dart'; import 'package:photos/utils/separators_util.dart'; typedef FutureVoidCallbackParamStr = Future Function(String); @@ -164,6 +165,7 @@ class TextInputDialog extends StatefulWidget { final String? message; final FutureVoidCallbackParamStr onConfirm; final String? hintText; + final IconData? prefixIcon; const TextInputDialog({ required this.title, this.body, @@ -173,6 +175,7 @@ class TextInputDialog extends StatefulWidget { this.label, this.message, this.hintText, + this.prefixIcon, super.key, }); @@ -187,64 +190,6 @@ class _TextInputDialogState extends State { final widthOfScreen = MediaQuery.of(context).size.width; final isMobileSmall = widthOfScreen <= mobileSmallThreshold; final colorScheme = getEnteColorScheme(context); - final textTheme = getEnteTextTheme(context); - var textInputChildren = []; - if (widget.label != null) textInputChildren.add(Text(widget.label!)); - textInputChildren.add( - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Material( - child: TextFormField( - controller: _textController, - decoration: InputDecoration( - hintText: widget.hintText, - hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), - filled: true, - contentPadding: const EdgeInsets.symmetric( - vertical: 11, - horizontal: 11, - ), - border: const UnderlineInputBorder( - borderSide: BorderSide.none, - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: colorScheme.strokeMuted), - borderRadius: BorderRadius.circular(8), - ), - prefixIconConstraints: const BoxConstraints( - maxHeight: 44, - maxWidth: 44, - minHeight: 44, - minWidth: 44, - ), - suffixIconConstraints: const BoxConstraints( - maxHeight: 44, - maxWidth: 44, - minHeight: 44, - minWidth: 44, - ), - prefixIcon: Icon( - Icons.search_outlined, - color: colorScheme.strokeMuted, - ), - ), - ), - ), - ), - ); - if (widget.message != null) { - textInputChildren.add( - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - widget.message!, - style: textTheme.small.copyWith(color: colorScheme.textMuted), - ), - ), - ); - } - textInputChildren = - addSeparators(textInputChildren, const SizedBox(height: 4)); return Container( width: min(widthOfScreen, 320), padding: isMobileSmall @@ -267,10 +212,12 @@ class _TextInputDialogState extends State { ), Padding( padding: const EdgeInsets.only(top: 19), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: textInputChildren, + child: TextInputWidget( + textController: _textController, + label: widget.label, + message: widget.message, + hintText: widget.hintText, + prefixIcon: widget.prefixIcon, ), ), const SizedBox(height: 36), diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart new file mode 100644 index 000000000..1b861c977 --- /dev/null +++ b/lib/ui/components/text_input_widget.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:photos/theme/ente_theme.dart'; +import 'package:photos/utils/separators_util.dart'; + +class TextInputWidget extends StatelessWidget { + final TextEditingController textController; + final String? label; + final String? message; + final String? hintText; + final IconData? prefixIcon; + const TextInputWidget({ + required this.textController, + this.label, + this.message, + this.hintText, + this.prefixIcon, + super.key, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + var textInputChildren = []; + if (label != null) textInputChildren.add(Text(label!)); + textInputChildren.add( + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Material( + child: TextFormField( + controller: textController, + decoration: InputDecoration( + hintText: hintText, + hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), + filled: true, + contentPadding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 12, + ), + border: const UnderlineInputBorder( + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: colorScheme.strokeMuted), + borderRadius: BorderRadius.circular(8), + ), + prefixIconConstraints: const BoxConstraints( + maxHeight: 44, + maxWidth: 44, + minHeight: 44, + minWidth: 44, + ), + suffixIconConstraints: const BoxConstraints( + maxHeight: 44, + maxWidth: 44, + minHeight: 44, + minWidth: 44, + ), + prefixIcon: prefixIcon != null + ? Icon( + prefixIcon, + color: colorScheme.strokeMuted, + ) + : null, + ), + ), + ), + ), + ); + if (message != null) { + textInputChildren.add( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + message!, + style: textTheme.small.copyWith(color: colorScheme.textMuted), + ), + ), + ); + } + textInputChildren = + addSeparators(textInputChildren, const SizedBox(height: 4)); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: textInputChildren, + ); + } +} diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 7699cdae8..2701ac990 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -261,6 +261,7 @@ Future showTextInputDialog( String? message, String? hintText, required FutureVoidCallbackParamStr onConfirm, + IconData? prefixIcon, }) { return showDialog( context: context, @@ -275,6 +276,7 @@ Future showTextInputDialog( confirmationButtonLabel: confirmationButtonLabel, onConfirm: onConfirm, hintText: hintText, + prefixIcon: prefixIcon, ), ); }, From 335c464180be306a49ba45d3ce5fca1bb8199d53 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 14:01:49 +0530 Subject: [PATCH 06/29] Made alignment of message and initial value of TextInputWidget configurable --- lib/ui/components/dialog_widget.dart | 6 ++++++ lib/ui/components/text_input_widget.dart | 14 +++++++++++--- lib/utils/dialog_util.dart | 4 ++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index e7e387d71..6d8263930 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -166,6 +166,8 @@ class TextInputDialog extends StatefulWidget { final FutureVoidCallbackParamStr onConfirm; final String? hintText; final IconData? prefixIcon; + final String? initialValue; + final Alignment? alignMessage; const TextInputDialog({ required this.title, this.body, @@ -176,6 +178,8 @@ class TextInputDialog extends StatefulWidget { this.message, this.hintText, this.prefixIcon, + this.initialValue, + this.alignMessage, super.key, }); @@ -218,6 +222,8 @@ class _TextInputDialogState extends State { message: widget.message, hintText: widget.hintText, prefixIcon: widget.prefixIcon, + initialValue: widget.initialValue, + alignMessage: widget.alignMessage, ), ), const SizedBox(height: 36), diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 1b861c977..3e7aa8899 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -8,17 +8,22 @@ class TextInputWidget extends StatelessWidget { final String? message; final String? hintText; final IconData? prefixIcon; + final String? initialValue; + final Alignment? alignMessage; const TextInputWidget({ required this.textController, this.label, this.message, this.hintText, this.prefixIcon, + this.initialValue, + this.alignMessage, super.key, }); @override Widget build(BuildContext context) { + initialValue != null ? textController.text = initialValue! : null; final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); var textInputChildren = []; @@ -71,9 +76,12 @@ class TextInputWidget extends StatelessWidget { textInputChildren.add( Padding( padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - message!, - style: textTheme.small.copyWith(color: colorScheme.textMuted), + child: Align( + alignment: alignMessage ?? Alignment.centerLeft, + child: Text( + message!, + style: textTheme.small.copyWith(color: colorScheme.textMuted), + ), ), ), ); diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 2701ac990..16ee1d297 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -262,6 +262,8 @@ Future showTextInputDialog( String? hintText, required FutureVoidCallbackParamStr onConfirm, IconData? prefixIcon, + String? initialValue, + Alignment? alignMessage, }) { return showDialog( context: context, @@ -277,6 +279,8 @@ Future showTextInputDialog( onConfirm: onConfirm, hintText: hintText, prefixIcon: prefixIcon, + initialValue: initialValue, + alignMessage: alignMessage, ), ); }, From c32abe66bea13a29daff262645882005603171a6 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 14:37:19 +0530 Subject: [PATCH 07/29] Made auto focus and max length configurable in TextInputWidget --- lib/ui/components/dialog_widget.dart | 4 ++++ lib/ui/components/text_input_widget.dart | 13 ++++++++++++- lib/utils/dialog_util.dart | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index 6d8263930..118e9b052 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -168,6 +168,7 @@ class TextInputDialog extends StatefulWidget { final IconData? prefixIcon; final String? initialValue; final Alignment? alignMessage; + final int? maxLength; const TextInputDialog({ required this.title, this.body, @@ -180,6 +181,7 @@ class TextInputDialog extends StatefulWidget { this.prefixIcon, this.initialValue, this.alignMessage, + this.maxLength, super.key, }); @@ -224,6 +226,8 @@ class _TextInputDialogState extends State { prefixIcon: widget.prefixIcon, initialValue: widget.initialValue, alignMessage: widget.alignMessage, + autoFocus: true, + maxLength: widget.maxLength, ), ), const SizedBox(height: 36), diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 3e7aa8899..84b78e949 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/utils/separators_util.dart'; @@ -10,6 +11,8 @@ class TextInputWidget extends StatelessWidget { final IconData? prefixIcon; final String? initialValue; final Alignment? alignMessage; + final bool? autoFocus; + final int? maxLength; const TextInputWidget({ required this.textController, this.label, @@ -18,12 +21,16 @@ class TextInputWidget extends StatelessWidget { this.prefixIcon, this.initialValue, this.alignMessage, + this.autoFocus, + this.maxLength, super.key, }); @override Widget build(BuildContext context) { - initialValue != null ? textController.text = initialValue! : null; + if (initialValue != null) { + textController.text = initialValue!; + } final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); var textInputChildren = []; @@ -33,7 +40,11 @@ class TextInputWidget extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(8)), child: Material( child: TextFormField( + autofocus: autoFocus ?? false, controller: textController, + inputFormatters: maxLength != null + ? [LengthLimitingTextInputFormatter(50)] + : null, decoration: InputDecoration( hintText: hintText, hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 16ee1d297..059cba64b 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -264,6 +264,7 @@ Future showTextInputDialog( IconData? prefixIcon, String? initialValue, Alignment? alignMessage, + int? maxLength, }) { return showDialog( context: context, @@ -281,6 +282,7 @@ Future showTextInputDialog( prefixIcon: prefixIcon, initialValue: initialValue, alignMessage: alignMessage, + maxLength: maxLength, ), ); }, From 8602a68a1d6455eca54b7ba9510ce172ae3e8e3d Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 14:39:34 +0530 Subject: [PATCH 08/29] Used new TextInputDialog for renaming a file --- lib/utils/magic_util.dart | 59 ++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/lib/utils/magic_util.dart b/lib/utils/magic_util.dart index e8d2ef1fd..7e8a37c0d 100644 --- a/lib/utils/magic_util.dart +++ b/lib/utils/magic_util.dart @@ -9,7 +9,6 @@ import 'package:photos/models/magic_metadata.dart'; import 'package:photos/services/collections_service.dart'; import 'package:photos/services/file_magic_service.dart'; import 'package:photos/ui/common/progress_dialog.dart'; -import 'package:photos/ui/common/rename_dialog.dart'; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/toast_util.dart'; @@ -94,37 +93,39 @@ Future editTime( } } -Future editFilename( +Future editFilename( BuildContext context, File file, ) async { - try { - final fileName = file.displayName; - final nameWithoutExt = basenameWithoutExtension(fileName); - final extName = extension(fileName); - var result = await showDialog( - context: context, - builder: (BuildContext context) { - return RenameDialog(nameWithoutExt, 'file', maxLength: 50); - }, - barrierColor: Colors.black.withOpacity(0.85), - ); - - if (result == null || result.trim() == nameWithoutExt.trim()) { - return true; - } - result = result + extName; - await _updatePublicMetadata( - context, - List.of([file]), - pubMagicKeyEditedName, - result, - ); - return true; - } catch (e) { - showShortToast(context, 'Something went wrong'); - return false; - } + final fileName = file.displayName; + final nameWithoutExt = basenameWithoutExtension(fileName); + final extName = extension(fileName); + await showTextInputDialog( + context, + title: "Rename file", + confirmationButtonLabel: "Rename", + initialValue: nameWithoutExt, + message: extName, + alignMessage: Alignment.centerRight, + hintText: "Enter file name", + maxLength: 50, + onConfirm: (String text) async { + try { + if (text.isEmpty || text.trim() == nameWithoutExt.trim()) { + return; + } + final newName = text + extName; + await _updatePublicMetadata( + context, + List.of([file]), + pubMagicKeyEditedName, + newName, + ); + } catch (e) { + showShortToast(context, 'Something went wrong'); + } + }, + ); } Future editFileCaption( From de553ddee8d89faa781ea5f3204c75b6324880dd Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 16:07:23 +0530 Subject: [PATCH 09/29] Fix: Move TextInputDialog up when keyboard is visible --- lib/utils/dialog_util.dart | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 059cba64b..20d325b97 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -269,20 +269,26 @@ Future showTextInputDialog( return showDialog( context: context, builder: (context) { - return Center( - child: TextInputDialog( - title: title, - message: message, - label: label, - body: body, - icon: icon, - confirmationButtonLabel: confirmationButtonLabel, - onConfirm: onConfirm, - hintText: hintText, - prefixIcon: prefixIcon, - initialValue: initialValue, - alignMessage: alignMessage, - maxLength: maxLength, + final bottomInset = MediaQuery.of(context).viewInsets.bottom; + final isKeyboardUp = bottomInset > 100; + return Align( + alignment: isKeyboardUp ? Alignment.bottomCenter : Alignment.center, + child: Padding( + padding: EdgeInsets.only(bottom: isKeyboardUp ? bottomInset + 24 : 0), + child: TextInputDialog( + title: title, + message: message, + label: label, + body: body, + icon: icon, + confirmationButtonLabel: confirmationButtonLabel, + onConfirm: onConfirm, + hintText: hintText, + prefixIcon: prefixIcon, + initialValue: initialValue, + alignMessage: alignMessage, + maxLength: maxLength, + ), ), ); }, From 3a218a656be639160bfc120f93e1e49a25320cfb Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 16:20:55 +0530 Subject: [PATCH 10/29] Set cursor position to the end of textField on initial focus --- lib/ui/components/text_input_widget.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 84b78e949..d8b7c19f1 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -29,7 +29,10 @@ class TextInputWidget extends StatelessWidget { @override Widget build(BuildContext context) { if (initialValue != null) { - textController.text = initialValue!; + textController.value = TextEditingValue( + text: initialValue!, + selection: TextSelection.collapsed(offset: initialValue!.length), + ); } final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); From e4d4e1d3d79ac3ef93a3167c71690afffce39500 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 16:44:02 +0530 Subject: [PATCH 11/29] Rename file: Removed old progress dialogs --- lib/ui/components/dialog_widget.dart | 1 + lib/utils/magic_util.dart | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index 118e9b052..4b5601bb4 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -250,6 +250,7 @@ class _TextInputDialogState extends State { labelText: widget.confirmationButtonLabel, onTap: _onTap, isInAlert: true, + shouldShowSuccessConfirmation: true, ), ), ], diff --git a/lib/utils/magic_util.dart b/lib/utils/magic_util.dart index 7e8a37c0d..94be4fa2c 100644 --- a/lib/utils/magic_util.dart +++ b/lib/utils/magic_util.dart @@ -120,9 +120,12 @@ Future editFilename( List.of([file]), pubMagicKeyEditedName, newName, + showProgressDialogs: false, + showDoneToast: false, ); } catch (e) { showShortToast(context, 'Something went wrong'); + rethrow; } }, ); @@ -156,12 +159,13 @@ Future _updatePublicMetadata( String key, dynamic value, { bool showDoneToast = true, + bool showProgressDialogs = true, }) async { if (files.isEmpty) { return; } ProgressDialog? dialog; - if (context != null) { + if (context != null && showProgressDialogs) { dialog = createProgressDialog(context, 'Please wait...'); await dialog.show(); } From b5c0ee7ce6d5b34dece633746ee15b05f8883de1 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 7 Feb 2023 17:59:43 +0530 Subject: [PATCH 12/29] Minor changes --- lib/utils/dialog_util.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 20d325b97..d52dcdb55 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:confetti/confetti.dart'; import 'package:flutter/material.dart'; import 'package:photos/core/constants.dart'; +import 'package:photos/theme/colors.dart'; import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/common/progress_dialog.dart'; import 'package:photos/ui/components/action_sheet_widget.dart'; @@ -267,14 +268,14 @@ Future showTextInputDialog( int? maxLength, }) { return showDialog( + barrierColor: backdropFaintDark, context: context, builder: (context) { final bottomInset = MediaQuery.of(context).viewInsets.bottom; final isKeyboardUp = bottomInset > 100; - return Align( - alignment: isKeyboardUp ? Alignment.bottomCenter : Alignment.center, + return Center( child: Padding( - padding: EdgeInsets.only(bottom: isKeyboardUp ? bottomInset + 24 : 0), + padding: EdgeInsets.only(bottom: isKeyboardUp ? bottomInset : 0), child: TextInputDialog( title: title, message: message, From 5208ff3e13b0ac07a57e47442e34b63584330fee Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 11:13:46 +0530 Subject: [PATCH 13/29] Moved execution of onSubmit from ButtonWidget associated with TextInputWidget to within TextInputWidget --- lib/ui/components/dialog_widget.dart | 26 ++++---- lib/ui/components/text_input_widget.dart | 63 ++++++++++++++----- .../gallery/gallery_app_bar_widget.dart | 4 +- lib/utils/dialog_util.dart | 8 +-- lib/utils/magic_util.dart | 4 +- 5 files changed, 66 insertions(+), 39 deletions(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index 4b5601bb4..af7c4370e 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -159,11 +159,11 @@ class Actions extends StatelessWidget { class TextInputDialog extends StatefulWidget { final String title; final String? body; - final String confirmationButtonLabel; + final String submitButtonLabel; final IconData? icon; final String? label; final String? message; - final FutureVoidCallbackParamStr onConfirm; + final FutureVoidCallbackParamStr onSubmit; final String? hintText; final IconData? prefixIcon; final String? initialValue; @@ -172,8 +172,8 @@ class TextInputDialog extends StatefulWidget { const TextInputDialog({ required this.title, this.body, - required this.confirmationButtonLabel, - required this.onConfirm, + required this.submitButtonLabel, + required this.onSubmit, this.icon, this.label, this.message, @@ -190,7 +190,8 @@ class TextInputDialog extends StatefulWidget { } class _TextInputDialogState extends State { - final TextEditingController _textController = TextEditingController(); + //the value of this ValueNotifier has no significance + final _submitNotifier = ValueNotifier(false); @override Widget build(BuildContext context) { final widthOfScreen = MediaQuery.of(context).size.width; @@ -219,7 +220,6 @@ class _TextInputDialogState extends State { Padding( padding: const EdgeInsets.only(top: 19), child: TextInputWidget( - textController: _textController, label: widget.label, message: widget.message, hintText: widget.hintText, @@ -228,6 +228,8 @@ class _TextInputDialogState extends State { alignMessage: widget.alignMessage, autoFocus: true, maxLength: widget.maxLength, + submitNotifier: _submitNotifier, + onSubmit: widget.onSubmit, ), ), const SizedBox(height: 36), @@ -247,10 +249,10 @@ class _TextInputDialogState extends State { child: ButtonWidget( buttonSize: ButtonSize.small, buttonType: ButtonType.neutral, - labelText: widget.confirmationButtonLabel, - onTap: _onTap, - isInAlert: true, - shouldShowSuccessConfirmation: true, + labelText: widget.submitButtonLabel, + onTap: () async { + _submitNotifier.value = !_submitNotifier.value; + }, ), ), ], @@ -260,8 +262,4 @@ class _TextInputDialogState extends State { ), ); } - - Future _onTap() async { - await widget.onConfirm.call(_textController.text); - } } diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index d8b7c19f1..d334e5010 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:photos/theme/ente_theme.dart'; +import 'package:photos/ui/components/dialog_widget.dart'; import 'package:photos/utils/separators_util.dart'; -class TextInputWidget extends StatelessWidget { - final TextEditingController textController; +class TextInputWidget extends StatefulWidget { final String? label; final String? message; final String? hintText; @@ -13,8 +13,12 @@ class TextInputWidget extends StatelessWidget { final Alignment? alignMessage; final bool? autoFocus; final int? maxLength; + final ValueNotifier? submitNotifier; + final bool alwaysShowSuccessState; + final bool showOnlyLoadingState; + final FutureVoidCallbackParamStr onSubmit; const TextInputWidget({ - required this.textController, + required this.onSubmit, this.label, this.message, this.hintText, @@ -23,33 +27,57 @@ class TextInputWidget extends StatelessWidget { this.alignMessage, this.autoFocus, this.maxLength, + this.submitNotifier, + this.alwaysShowSuccessState = false, + this.showOnlyLoadingState = false, super.key, }); + @override + State createState() => _TextInputWidgetState(); +} + +class _TextInputWidgetState extends State { + final _textController = TextEditingController(); + + @override + void initState() { + widget.submitNotifier?.addListener(() { + widget.onSubmit.call(_textController.text); + }); + super.initState(); + } + + @override + void dispose() { + widget.submitNotifier?.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - if (initialValue != null) { - textController.value = TextEditingValue( - text: initialValue!, - selection: TextSelection.collapsed(offset: initialValue!.length), + if (widget.initialValue != null) { + _textController.value = TextEditingValue( + text: widget.initialValue!, + selection: TextSelection.collapsed(offset: widget.initialValue!.length), ); } final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); var textInputChildren = []; - if (label != null) textInputChildren.add(Text(label!)); + if (widget.label != null) textInputChildren.add(Text(widget.label!)); textInputChildren.add( ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(8)), child: Material( child: TextFormField( - autofocus: autoFocus ?? false, - controller: textController, - inputFormatters: maxLength != null + autofocus: widget.autoFocus ?? false, + controller: _textController, + inputFormatters: widget.maxLength != null ? [LengthLimitingTextInputFormatter(50)] : null, decoration: InputDecoration( - hintText: hintText, + hintText: widget.hintText, hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), filled: true, contentPadding: const EdgeInsets.symmetric( @@ -75,25 +103,26 @@ class TextInputWidget extends StatelessWidget { minHeight: 44, minWidth: 44, ), - prefixIcon: prefixIcon != null + prefixIcon: widget.prefixIcon != null ? Icon( - prefixIcon, + widget.prefixIcon, color: colorScheme.strokeMuted, ) : null, ), + onEditingComplete: () {}, ), ), ), ); - if (message != null) { + if (widget.message != null) { textInputChildren.add( Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Align( - alignment: alignMessage ?? Alignment.centerLeft, + alignment: widget.alignMessage ?? Alignment.centerLeft, child: Text( - message!, + widget.message!, style: textTheme.small.copyWith(color: colorScheme.textMuted), ), ), diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 1f214c0e4..f55336303 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -114,9 +114,9 @@ class _GalleryAppBarWidgetState extends State { final result = await showTextInputDialog( context, title: "Rename album", - confirmationButtonLabel: "Rename", + submitButtonLabel: "Rename", hintText: "Enter album name", - onConfirm: (String text) async { + onSubmit: (String text) async { // indicates user cancelled the rename request if (text == "" || text.trim() == _appBarTitle!.trim()) { return; diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index d52dcdb55..da8ec5069 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -256,12 +256,12 @@ Future showTextInputDialog( BuildContext context, { required String title, String? body, - required String confirmationButtonLabel, + required String submitButtonLabel, IconData? icon, String? label, String? message, String? hintText, - required FutureVoidCallbackParamStr onConfirm, + required FutureVoidCallbackParamStr onSubmit, IconData? prefixIcon, String? initialValue, Alignment? alignMessage, @@ -282,8 +282,8 @@ Future showTextInputDialog( label: label, body: body, icon: icon, - confirmationButtonLabel: confirmationButtonLabel, - onConfirm: onConfirm, + submitButtonLabel: submitButtonLabel, + onSubmit: onSubmit, hintText: hintText, prefixIcon: prefixIcon, initialValue: initialValue, diff --git a/lib/utils/magic_util.dart b/lib/utils/magic_util.dart index 94be4fa2c..558182df6 100644 --- a/lib/utils/magic_util.dart +++ b/lib/utils/magic_util.dart @@ -103,13 +103,13 @@ Future editFilename( await showTextInputDialog( context, title: "Rename file", - confirmationButtonLabel: "Rename", + submitButtonLabel: "Rename", initialValue: nameWithoutExt, message: extName, alignMessage: Alignment.centerRight, hintText: "Enter file name", maxLength: 50, - onConfirm: (String text) async { + onSubmit: (String text) async { try { if (text.isEmpty || text.trim() == nameWithoutExt.trim()) { return; From 1bc3bde3d18ad7d11ec38329708381ebe5ac6c09 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 11:26:09 +0530 Subject: [PATCH 14/29] Added debouncer and code for execution states in TextInputWidget --- lib/ui/components/text_input_widget.dart | 49 +++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index d334e5010..e48f2a7e6 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -2,8 +2,16 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/components/dialog_widget.dart'; +import 'package:photos/utils/debouncer.dart'; import 'package:photos/utils/separators_util.dart'; +enum ExecutionState { + idle, + inProgress, + error, + successful; +} + class TextInputWidget extends StatefulWidget { final String? label; final String? message; @@ -39,11 +47,14 @@ class TextInputWidget extends StatefulWidget { class _TextInputWidgetState extends State { final _textController = TextEditingController(); + final _debouncer = Debouncer(const Duration(milliseconds: 300)); + final ValueNotifier _executionStateNotifier = + ValueNotifier(ExecutionState.idle); @override void initState() { widget.submitNotifier?.addListener(() { - widget.onSubmit.call(_textController.text); + _onSubmit(); }); super.initState(); } @@ -51,6 +62,7 @@ class _TextInputWidgetState extends State { @override void dispose() { widget.submitNotifier?.dispose(); + _executionStateNotifier.dispose(); super.dispose(); } @@ -137,4 +149,39 @@ class _TextInputWidgetState extends State { children: textInputChildren, ); } + + Future _onSubmit() async { + _debouncer.run( + () => Future( + () { + _executionStateNotifier.value = ExecutionState.inProgress; + }, + ), + ); + await widget.onSubmit.call(_textController.text).then( + (value) { + widget.alwaysShowSuccessState + ? _executionStateNotifier.value = ExecutionState.successful + : null; + }, + onError: (error, stackTrace) => _debouncer.cancelDebounce(), + ); + _debouncer.cancelDebounce(); + if (widget.alwaysShowSuccessState) { + Future.delayed(const Duration(seconds: 2), () { + _executionStateNotifier.value = ExecutionState.idle; + }); + return; + } + if (_executionStateNotifier.value == ExecutionState.inProgress) { + if (widget.showOnlyLoadingState) { + _executionStateNotifier.value = ExecutionState.idle; + } else { + _executionStateNotifier.value = ExecutionState.successful; + Future.delayed(const Duration(seconds: 2), () { + _executionStateNotifier.value = ExecutionState.idle; + }); + } + } + } } From d9333610acf8906c225a89bebecdf66d7247f0a6 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 12:30:56 +0530 Subject: [PATCH 15/29] Show execution states of TextInputWidget on UI --- lib/ui/components/text_input_widget.dart | 61 ++++++++++++++++++++---- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index e48f2a7e6..830d74439 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:photos/theme/ente_theme.dart'; +import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/components/dialog_widget.dart'; import 'package:photos/utils/debouncer.dart'; import 'package:photos/utils/separators_util.dart'; @@ -36,7 +37,7 @@ class TextInputWidget extends StatefulWidget { this.autoFocus, this.maxLength, this.submitNotifier, - this.alwaysShowSuccessState = false, + this.alwaysShowSuccessState = true, this.showOnlyLoadingState = false, super.key, }); @@ -56,6 +57,9 @@ class _TextInputWidgetState extends State { widget.submitNotifier?.addListener(() { _onSubmit(); }); + _executionStateNotifier.addListener(() { + setState(() {}); + }); super.initState(); } @@ -92,9 +96,11 @@ class _TextInputWidgetState extends State { hintText: widget.hintText, hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), filled: true, - contentPadding: const EdgeInsets.symmetric( - vertical: 12, - horizontal: 12, + contentPadding: const EdgeInsets.fromLTRB( + 12, + 12, + 0, + 12, ), border: const UnderlineInputBorder( borderSide: BorderSide.none, @@ -103,6 +109,18 @@ class _TextInputWidgetState extends State { borderSide: BorderSide(color: colorScheme.strokeMuted), borderRadius: BorderRadius.circular(8), ), + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 12), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 175), + switchInCurve: Curves.easeInExpo, + switchOutCurve: Curves.easeOutExpo, + child: SuffixIconWidget( + _executionStateNotifier.value, + key: ValueKey(_executionStateNotifier.value), + ), + ), + ), prefixIconConstraints: const BoxConstraints( maxHeight: 44, maxWidth: 44, @@ -110,10 +128,10 @@ class _TextInputWidgetState extends State { minWidth: 44, ), suffixIconConstraints: const BoxConstraints( - maxHeight: 44, - maxWidth: 44, - minHeight: 44, - minWidth: 44, + maxHeight: 24, + maxWidth: 36, + minHeight: 24, + minWidth: 36, ), prefixIcon: widget.prefixIcon != null ? Icon( @@ -185,3 +203,30 @@ class _TextInputWidgetState extends State { } } } + +class SuffixIconWidget extends StatelessWidget { + final ExecutionState executionState; + const SuffixIconWidget(this.executionState, {super.key}); + + @override + Widget build(BuildContext context) { + final Widget trailingWidget; + final colorScheme = getEnteColorScheme(context); + if (executionState == ExecutionState.idle) { + trailingWidget = const SizedBox.shrink(); + } else if (executionState == ExecutionState.inProgress) { + trailingWidget = EnteLoadingWidget( + color: colorScheme.strokeMuted, + ); + } else if (executionState == ExecutionState.successful) { + trailingWidget = Icon( + Icons.check_outlined, + size: 22, + color: colorScheme.primary500, + ); + } else { + trailingWidget = const SizedBox.shrink(); + } + return trailingWidget; + } +} From e19076a46a054712a22d5a2a1efbbfb638f9f73b Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 12:54:35 +0530 Subject: [PATCH 16/29] Minor layout and state fixes --- lib/ui/components/text_input_widget.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 830d74439..9c6e6da4d 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -60,6 +60,12 @@ class _TextInputWidgetState extends State { _executionStateNotifier.addListener(() { setState(() {}); }); + if (widget.initialValue != null) { + _textController.value = TextEditingValue( + text: widget.initialValue!, + selection: TextSelection.collapsed(offset: widget.initialValue!.length), + ); + } super.initState(); } @@ -72,12 +78,6 @@ class _TextInputWidgetState extends State { @override Widget build(BuildContext context) { - if (widget.initialValue != null) { - _textController.value = TextEditingValue( - text: widget.initialValue!, - selection: TextSelection.collapsed(offset: widget.initialValue!.length), - ); - } final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); var textInputChildren = []; @@ -96,6 +96,7 @@ class _TextInputWidgetState extends State { hintText: widget.hintText, hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), filled: true, + fillColor: colorScheme.fillFaint, contentPadding: const EdgeInsets.fromLTRB( 12, 12, @@ -110,7 +111,7 @@ class _TextInputWidgetState extends State { borderRadius: BorderRadius.circular(8), ), suffixIcon: Padding( - padding: const EdgeInsets.only(right: 12), + padding: const EdgeInsets.symmetric(horizontal: 12), child: AnimatedSwitcher( duration: const Duration(milliseconds: 175), switchInCurve: Curves.easeInExpo, @@ -129,9 +130,9 @@ class _TextInputWidgetState extends State { ), suffixIconConstraints: const BoxConstraints( maxHeight: 24, - maxWidth: 36, + maxWidth: 48, minHeight: 24, - minWidth: 36, + minWidth: 48, ), prefixIcon: widget.prefixIcon != null ? Icon( From c03a5ee19410faa744cd3fd90611bbac6773922e Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 15:20:51 +0530 Subject: [PATCH 17/29] Changed code for setting execution states + other minor fixes --- lib/ui/components/dialog_widget.dart | 1 + lib/ui/components/text_input_widget.dart | 130 ++++++++++++++++------- 2 files changed, 94 insertions(+), 37 deletions(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index af7c4370e..a8aaee894 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -230,6 +230,7 @@ class _TextInputDialogState extends State { maxLength: widget.maxLength, submitNotifier: _submitNotifier, onSubmit: widget.onSubmit, + popNavAfterSubmission: true, ), ), const SizedBox(height: 36), diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 9c6e6da4d..846ad8fde 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -26,6 +26,8 @@ class TextInputWidget extends StatefulWidget { final bool alwaysShowSuccessState; final bool showOnlyLoadingState; final FutureVoidCallbackParamStr onSubmit; + final bool popNavAfterSubmission; + final bool shouldSurfaceExecutionStates; const TextInputWidget({ required this.onSubmit, this.label, @@ -37,8 +39,10 @@ class TextInputWidget extends StatefulWidget { this.autoFocus, this.maxLength, this.submitNotifier, - this.alwaysShowSuccessState = true, + this.alwaysShowSuccessState = false, this.showOnlyLoadingState = false, + this.popNavAfterSubmission = false, + this.shouldSurfaceExecutionStates = true, super.key, }); @@ -47,19 +51,16 @@ class TextInputWidget extends StatefulWidget { } class _TextInputWidgetState extends State { + ExecutionState executionState = ExecutionState.idle; final _textController = TextEditingController(); final _debouncer = Debouncer(const Duration(milliseconds: 300)); - final ValueNotifier _executionStateNotifier = - ValueNotifier(ExecutionState.idle); @override void initState() { widget.submitNotifier?.addListener(() { _onSubmit(); }); - _executionStateNotifier.addListener(() { - setState(() {}); - }); + if (widget.initialValue != null) { _textController.value = TextEditingValue( text: widget.initialValue!, @@ -72,12 +73,19 @@ class _TextInputWidgetState extends State { @override void dispose() { widget.submitNotifier?.dispose(); - _executionStateNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { + if (executionState == ExecutionState.successful) { + Future.delayed(Duration(seconds: widget.popNavAfterSubmission ? 1 : 2), + () { + setState(() { + executionState = ExecutionState.idle; + }); + }); + } final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); var textInputChildren = []; @@ -117,8 +125,10 @@ class _TextInputWidgetState extends State { switchInCurve: Curves.easeInExpo, switchOutCurve: Curves.easeOutExpo, child: SuffixIconWidget( - _executionStateNotifier.value, - key: ValueKey(_executionStateNotifier.value), + key: ValueKey(executionState), + executionState: executionState, + shouldSurfaceExecutionStates: + widget.shouldSurfaceExecutionStates, ), ), ), @@ -169,51 +179,97 @@ class _TextInputWidgetState extends State { ); } - Future _onSubmit() async { + void _onSubmit() async { _debouncer.run( - () => Future( - () { - _executionStateNotifier.value = ExecutionState.inProgress; - }, - ), - ); - await widget.onSubmit.call(_textController.text).then( - (value) { - widget.alwaysShowSuccessState - ? _executionStateNotifier.value = ExecutionState.successful - : null; - }, - onError: (error, stackTrace) => _debouncer.cancelDebounce(), + () => Future(() { + setState(() { + executionState = ExecutionState.inProgress; + }); + }), ); + await widget.onSubmit! + .call(_textController.text) + .onError((error, stackTrace) { + executionState = ExecutionState.error; + _debouncer.cancelDebounce(); + }); + widget.alwaysShowSuccessState && _debouncer.isActive() + ? executionState = ExecutionState.successful + : null; _debouncer.cancelDebounce(); - if (widget.alwaysShowSuccessState) { - Future.delayed(const Duration(seconds: 2), () { - _executionStateNotifier.value = ExecutionState.idle; - }); - return; + if (executionState == ExecutionState.successful) { + setState(() {}); } - if (_executionStateNotifier.value == ExecutionState.inProgress) { - if (widget.showOnlyLoadingState) { - _executionStateNotifier.value = ExecutionState.idle; - } else { - _executionStateNotifier.value = ExecutionState.successful; - Future.delayed(const Duration(seconds: 2), () { - _executionStateNotifier.value = ExecutionState.idle; + + // when the time taken by widget.onSubmit is approximately equal to the debounce + // time, the callback is getting executed when/after the if condition + // below is executing/executed which results in execution state stuck at + // idle state. This Future is for delaying the execution of the if + // condition so that the calback in the debouncer finishes execution before. + await Future.delayed(const Duration(milliseconds: 5)); + if (executionState == ExecutionState.inProgress || + executionState == ExecutionState.error) { + if (executionState == ExecutionState.inProgress) { + if (mounted) { + setState(() { + executionState = ExecutionState.successful; + Future.delayed( + Duration( + seconds: widget.shouldSurfaceExecutionStates + ? (widget.popNavAfterSubmission ? 1 : 2) + : 0, + ), () { + widget.popNavAfterSubmission ? _popNavigatorStack(context) : null; + if (mounted) { + setState(() { + executionState = ExecutionState.idle; + }); + } + }); + }); + } + } + if (executionState == ExecutionState.error) { + setState(() { + executionState = ExecutionState.idle; + widget.popNavAfterSubmission + ? Future.delayed( + const Duration(seconds: 0), + () => _popNavigatorStack(context), + ) + : null; }); } + } else { + if (widget.popNavAfterSubmission) { + Future.delayed( + Duration(seconds: widget.alwaysShowSuccessState ? 1 : 0), + () => _popNavigatorStack(context), + ); + } } } + + void _popNavigatorStack(BuildContext context) { + Navigator.of(context).canPop() ? Navigator.of(context).pop() : null; + } } class SuffixIconWidget extends StatelessWidget { final ExecutionState executionState; - const SuffixIconWidget(this.executionState, {super.key}); + final bool shouldSurfaceExecutionStates; + const SuffixIconWidget({ + required this.executionState, + required this.shouldSurfaceExecutionStates, + super.key, + }); @override Widget build(BuildContext context) { final Widget trailingWidget; final colorScheme = getEnteColorScheme(context); - if (executionState == ExecutionState.idle) { + if (executionState == ExecutionState.idle || + !shouldSurfaceExecutionStates) { trailingWidget = const SizedBox.shrink(); } else if (executionState == ExecutionState.inProgress) { trailingWidget = EnteLoadingWidget( From c42b9c3263a6dfb306fdaa0811887fb3765d98e6 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 17:06:04 +0530 Subject: [PATCH 18/29] Added code for only showing loading state for TextInputWidget --- lib/ui/components/text_input_widget.dart | 38 ++++++++++++++---------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 846ad8fde..fb0ace207 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -211,22 +211,30 @@ class _TextInputWidgetState extends State { executionState == ExecutionState.error) { if (executionState == ExecutionState.inProgress) { if (mounted) { - setState(() { - executionState = ExecutionState.successful; - Future.delayed( - Duration( - seconds: widget.shouldSurfaceExecutionStates - ? (widget.popNavAfterSubmission ? 1 : 2) - : 0, - ), () { - widget.popNavAfterSubmission ? _popNavigatorStack(context) : null; - if (mounted) { - setState(() { - executionState = ExecutionState.idle; - }); - } + if (widget.showOnlyLoadingState) { + setState(() { + executionState = ExecutionState.idle; }); - }); + } else { + setState(() { + executionState = ExecutionState.successful; + Future.delayed( + Duration( + seconds: widget.shouldSurfaceExecutionStates + ? (widget.popNavAfterSubmission ? 1 : 2) + : 0, + ), () { + widget.popNavAfterSubmission + ? _popNavigatorStack(context) + : null; + if (mounted) { + setState(() { + executionState = ExecutionState.idle; + }); + } + }); + }); + } } } if (executionState == ExecutionState.error) { From 9691bf9b66204fc99a3c6078ed0cbc397d3ae511 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 17:22:42 +0530 Subject: [PATCH 19/29] Capitalize file ext + sumbit on 'done' in keyboard --- lib/ui/components/text_input_widget.dart | 4 +++- lib/ui/viewer/file/file_info_widget.dart | 4 +++- lib/utils/magic_util.dart | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index fb0ace207..3755732ff 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -151,7 +151,9 @@ class _TextInputWidgetState extends State { ) : null, ), - onEditingComplete: () {}, + onEditingComplete: () { + _onSubmit(); + }, ), ), ), diff --git a/lib/ui/viewer/file/file_info_widget.dart b/lib/ui/viewer/file/file_info_widget.dart index 0edc37986..2a6e7b8f3 100644 --- a/lib/ui/viewer/file/file_info_widget.dart +++ b/lib/ui/viewer/file/file_info_widget.dart @@ -2,6 +2,7 @@ import "package:exif/exif.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; +import 'package:path/path.dart' as path; import 'package:photo_manager/photo_manager.dart'; import "package:photos/core/configuration.dart"; import 'package:photos/db/files_db.dart'; @@ -154,7 +155,8 @@ class _FileInfoWidgetState extends State { ), ), title: Text( - file.displayName, + path.basenameWithoutExtension(file.displayName) + + path.extension(file.displayName).toUpperCase(), ), subtitle: Row( children: [ diff --git a/lib/utils/magic_util.dart b/lib/utils/magic_util.dart index 558182df6..35c5ba5eb 100644 --- a/lib/utils/magic_util.dart +++ b/lib/utils/magic_util.dart @@ -105,7 +105,7 @@ Future editFilename( title: "Rename file", submitButtonLabel: "Rename", initialValue: nameWithoutExt, - message: extName, + message: extName.toUpperCase(), alignMessage: Alignment.centerRight, hintText: "Enter file name", maxLength: 50, From de03b1dd4463bfa2e1f1571018c0d84ff21302bb Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 17:39:57 +0530 Subject: [PATCH 20/29] Moved ExecutionState enum to a separate file --- lib/models/execution_states.dart | 6 ++++++ lib/ui/components/button_widget.dart | 8 +------- .../menu_item_widget/menu_item_child_widgets.dart | 2 +- lib/ui/components/menu_item_widget/menu_item_widget.dart | 8 +------- lib/ui/components/text_input_widget.dart | 8 +------- lib/ui/components/toggle_switch_widget.dart | 7 +------ 6 files changed, 11 insertions(+), 28 deletions(-) create mode 100644 lib/models/execution_states.dart diff --git a/lib/models/execution_states.dart b/lib/models/execution_states.dart new file mode 100644 index 000000000..764386c6e --- /dev/null +++ b/lib/models/execution_states.dart @@ -0,0 +1,6 @@ +enum ExecutionState { + idle, + inProgress, + error, + successful; +} diff --git a/lib/ui/components/button_widget.dart b/lib/ui/components/button_widget.dart index 64276f99b..8e19d4f4c 100644 --- a/lib/ui/components/button_widget.dart +++ b/lib/ui/components/button_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:photos/models/execution_states.dart'; import 'package:photos/theme/colors.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/theme/text_style.dart'; @@ -8,13 +9,6 @@ import 'package:photos/ui/components/models/button_type.dart'; import 'package:photos/ui/components/models/custom_button_style.dart'; import 'package:photos/utils/debouncer.dart'; -enum ExecutionState { - idle, - inProgress, - error, - successful; -} - enum ButtonSize { small, large; diff --git a/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart b/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart index 59f7f69a5..b380e07c3 100644 --- a/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart +++ b/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:photos/models/execution_states.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/common/loading_widget.dart'; -import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; class TrailingWidget extends StatefulWidget { final ValueNotifier executionStateNotifier; diff --git a/lib/ui/components/menu_item_widget/menu_item_widget.dart b/lib/ui/components/menu_item_widget/menu_item_widget.dart index f89d60a85..dc4dba21b 100644 --- a/lib/ui/components/menu_item_widget/menu_item_widget.dart +++ b/lib/ui/components/menu_item_widget/menu_item_widget.dart @@ -1,16 +1,10 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; +import 'package:photos/models/execution_states.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/components/menu_item_widget/menu_item_child_widgets.dart'; import 'package:photos/utils/debouncer.dart'; -enum ExecutionState { - idle, - inProgress, - error, - successful; -} - typedef FutureVoidCallback = Future Function(); class MenuItemWidget extends StatefulWidget { diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 3755732ff..2012670f1 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -1,18 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:photos/models/execution_states.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/components/dialog_widget.dart'; import 'package:photos/utils/debouncer.dart'; import 'package:photos/utils/separators_util.dart'; -enum ExecutionState { - idle, - inProgress, - error, - successful; -} - class TextInputWidget extends StatefulWidget { final String? label; final String? message; diff --git a/lib/ui/components/toggle_switch_widget.dart b/lib/ui/components/toggle_switch_widget.dart index 7b6a82084..51f6116fe 100644 --- a/lib/ui/components/toggle_switch_widget.dart +++ b/lib/ui/components/toggle_switch_widget.dart @@ -1,14 +1,9 @@ import 'package:flutter/material.dart'; import 'package:photos/ente_theme_data.dart'; +import 'package:photos/models/execution_states.dart'; import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/utils/debouncer.dart'; -enum ExecutionState { - idle, - inProgress, - successful, -} - typedef FutureVoidCallback = Future Function(); typedef BoolCallBack = bool Function(); From d9b28b43b2d250f250e32fd195d171f96763c137 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 17:57:36 +0530 Subject: [PATCH 21/29] Moved commonly used user defined function types to a file --- lib/core/error-reporting/super_logging.dart | 3 +-- lib/models/typedefs.dart | 6 ++++++ lib/ui/components/button_widget.dart | 3 +-- lib/ui/components/dialog_widget.dart | 3 +-- lib/ui/components/menu_item_widget/menu_item_widget.dart | 3 +-- lib/ui/components/text_input_widget.dart | 4 ++-- lib/ui/components/toggle_switch_widget.dart | 4 +--- lib/utils/dialog_util.dart | 1 + 8 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 lib/models/typedefs.dart diff --git a/lib/core/error-reporting/super_logging.dart b/lib/core/error-reporting/super_logging.dart index e9506ea28..a45c0fa4d 100644 --- a/lib/core/error-reporting/super_logging.dart +++ b/lib/core/error-reporting/super_logging.dart @@ -14,10 +14,9 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:photos/core/error-reporting/tunneled_transport.dart'; +import 'package:photos/models/typedefs.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -typedef FutureOrVoidCallback = FutureOr Function(); - extension SuperString on String { Iterable chunked(int chunkSize) sync* { var start = 0; diff --git a/lib/models/typedefs.dart b/lib/models/typedefs.dart new file mode 100644 index 000000000..5a0ba2bf2 --- /dev/null +++ b/lib/models/typedefs.dart @@ -0,0 +1,6 @@ +import 'dart:async'; + +typedef FutureVoidCallback = Future Function(); +typedef BoolCallBack = bool Function(); +typedef FutureVoidCallbackParamStr = Future Function(String); +typedef FutureOrVoidCallback = FutureOr Function(); diff --git a/lib/ui/components/button_widget.dart b/lib/ui/components/button_widget.dart index 8e19d4f4c..daa1b1ba3 100644 --- a/lib/ui/components/button_widget.dart +++ b/lib/ui/components/button_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:photos/models/execution_states.dart'; +import 'package:photos/models/typedefs.dart'; import 'package:photos/theme/colors.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/theme/text_style.dart'; @@ -23,8 +24,6 @@ enum ButtonAction { error; } -typedef FutureVoidCallback = Future Function(); - class ButtonWidget extends StatelessWidget { final IconData? icon; final String? labelText; diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index a8aaee894..1810a48ce 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:photos/core/constants.dart'; +import 'package:photos/models/typedefs.dart'; import 'package:photos/theme/colors.dart'; import 'package:photos/theme/effects.dart'; import 'package:photos/theme/ente_theme.dart'; @@ -10,8 +11,6 @@ import 'package:photos/ui/components/models/button_type.dart'; import 'package:photos/ui/components/text_input_widget.dart'; import 'package:photos/utils/separators_util.dart'; -typedef FutureVoidCallbackParamStr = Future Function(String); - ///Will return null if dismissed by tapping outside Future showDialogWidget({ required BuildContext context, diff --git a/lib/ui/components/menu_item_widget/menu_item_widget.dart b/lib/ui/components/menu_item_widget/menu_item_widget.dart index dc4dba21b..112af62e2 100644 --- a/lib/ui/components/menu_item_widget/menu_item_widget.dart +++ b/lib/ui/components/menu_item_widget/menu_item_widget.dart @@ -1,12 +1,11 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:photos/models/execution_states.dart'; +import 'package:photos/models/typedefs.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/components/menu_item_widget/menu_item_child_widgets.dart'; import 'package:photos/utils/debouncer.dart'; -typedef FutureVoidCallback = Future Function(); - class MenuItemWidget extends StatefulWidget { final Widget captionedTextWidget; final bool isExpandable; diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 2012670f1..ce6fe57d7 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:photos/models/execution_states.dart'; +import 'package:photos/models/typedefs.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/common/loading_widget.dart'; -import 'package:photos/ui/components/dialog_widget.dart'; import 'package:photos/utils/debouncer.dart'; import 'package:photos/utils/separators_util.dart'; @@ -183,7 +183,7 @@ class _TextInputWidgetState extends State { }); }), ); - await widget.onSubmit! + await widget.onSubmit .call(_textController.text) .onError((error, stackTrace) { executionState = ExecutionState.error; diff --git a/lib/ui/components/toggle_switch_widget.dart b/lib/ui/components/toggle_switch_widget.dart index 51f6116fe..c6a9077bf 100644 --- a/lib/ui/components/toggle_switch_widget.dart +++ b/lib/ui/components/toggle_switch_widget.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:photos/ente_theme_data.dart'; import 'package:photos/models/execution_states.dart'; +import 'package:photos/models/typedefs.dart'; import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/utils/debouncer.dart'; -typedef FutureVoidCallback = Future Function(); -typedef BoolCallBack = bool Function(); - class ToggleSwitchWidget extends StatefulWidget { final BoolCallBack value; final FutureVoidCallback onChanged; diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index da8ec5069..a065e4a6d 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:confetti/confetti.dart'; import 'package:flutter/material.dart'; import 'package:photos/core/constants.dart'; +import 'package:photos/models/typedefs.dart'; import 'package:photos/theme/colors.dart'; import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/common/progress_dialog.dart'; From 9a63900f46063bc9198dfd5bc4ec6ad22a710efc Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 8 Feb 2023 18:41:04 +0530 Subject: [PATCH 22/29] Used new TextInputDialog for naming an album on creating one --- lib/ui/components/dialog_widget.dart | 6 + lib/ui/components/text_input_widget.dart | 4 + lib/ui/create_collection_sheet.dart | 138 ++++++++++------------- lib/utils/dialog_util.dart | 4 + 4 files changed, 73 insertions(+), 79 deletions(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index 1810a48ce..b12958bbd 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -168,11 +168,14 @@ class TextInputDialog extends StatefulWidget { final String? initialValue; final Alignment? alignMessage; final int? maxLength; + final bool showOnlyLoadingState; + final TextCapitalization? textCapitalization; const TextInputDialog({ required this.title, this.body, required this.submitButtonLabel, required this.onSubmit, + required this.showOnlyLoadingState, this.icon, this.label, this.message, @@ -181,6 +184,7 @@ class TextInputDialog extends StatefulWidget { this.initialValue, this.alignMessage, this.maxLength, + this.textCapitalization, super.key, }); @@ -230,6 +234,8 @@ class _TextInputDialogState extends State { submitNotifier: _submitNotifier, onSubmit: widget.onSubmit, popNavAfterSubmission: true, + showOnlyLoadingState: widget.showOnlyLoadingState, + textCapitalization: widget.textCapitalization, ), ), const SizedBox(height: 36), diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index ce6fe57d7..fa4b67a27 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -22,6 +22,7 @@ class TextInputWidget extends StatefulWidget { final FutureVoidCallbackParamStr onSubmit; final bool popNavAfterSubmission; final bool shouldSurfaceExecutionStates; + final TextCapitalization? textCapitalization; const TextInputWidget({ required this.onSubmit, this.label, @@ -37,6 +38,7 @@ class TextInputWidget extends StatefulWidget { this.showOnlyLoadingState = false, this.popNavAfterSubmission = false, this.shouldSurfaceExecutionStates = true, + this.textCapitalization = TextCapitalization.none, super.key, }); @@ -89,6 +91,7 @@ class _TextInputWidgetState extends State { borderRadius: const BorderRadius.all(Radius.circular(8)), child: Material( child: TextFormField( + textCapitalization: widget.textCapitalization!, autofocus: widget.autoFocus ?? false, controller: _textController, inputFormatters: widget.maxLength != null @@ -211,6 +214,7 @@ class _TextInputWidgetState extends State { setState(() { executionState = ExecutionState.idle; }); + _popNavigatorStack(context); } else { setState(() { executionState = ExecutionState.successful; diff --git a/lib/ui/create_collection_sheet.dart b/lib/ui/create_collection_sheet.dart index c2f5d7ab6..c75ec1438 100644 --- a/lib/ui/create_collection_sheet.dart +++ b/lib/ui/create_collection_sheet.dart @@ -153,7 +153,16 @@ class _CreateCollectionSheetState extends State { widget.showOptionToCreateNewAlbum) { return GestureDetector( onTap: () { - _showNameAlbumDialog(); + showTextInputDialog( + context, + title: "Album title", + submitButtonLabel: "OK", + hintText: "Enter album name", + onSubmit: _nameAlbum, + showOnlyLoadingState: true, + textCapitalization: + TextCapitalization.words, + ); }, behavior: HitTestBehavior.opaque, child: @@ -225,84 +234,44 @@ class _CreateCollectionSheetState extends State { ); } - void _showNameAlbumDialog() async { - String? albumName; - final AlertDialog alert = AlertDialog( - title: const Text("Album title"), - content: TextFormField( - decoration: const InputDecoration( - hintText: "Christmas 2020 / Dinner at Alice's", - contentPadding: EdgeInsets.all(8), - ), - onChanged: (value) { - albumName = value; - }, - autofocus: true, - keyboardType: TextInputType.text, - textCapitalization: TextCapitalization.words, - ), - actions: [ - TextButton( - child: Text( - "Ok", - style: TextStyle( - color: getEnteColorScheme(context).primary500, - ), - ), - onPressed: () async { - if (albumName != null && albumName!.isNotEmpty) { - Navigator.of(context, rootNavigator: true).pop('dialog'); - final collection = await _createAlbum(albumName!); - if (collection != null) { - if (await _runCollectionAction(collection.id)) { - if (widget.actionType == CollectionActionType.restoreFiles) { - showShortToast( - context, - 'Restored files to album ' + albumName!, - ); - } else { - showShortToast( - context, - "Album '" + albumName! + "' created.", - ); - } - _navigateToCollection(collection); - } - } - } - }, - ), - ], - ); - - showDialog( - context: context, - builder: (BuildContext context) { - return alert; - }, - ); + Future _nameAlbum(String albumName) async { + if (albumName.isNotEmpty) { + final collection = await _createAlbum(albumName); + if (collection != null) { + if (await _runCollectionAction( + collectionID: collection.id, + showProgressDialog: false, + )) { + if (widget.actionType == CollectionActionType.restoreFiles) { + showShortToast( + context, + 'Restored files to album ' + albumName, + ); + } else { + showShortToast( + context, + "Album '" + albumName + "' created.", + ); + } + _navigateToCollection(collection); + } + } + } } Future _createAlbum(String albumName) async { Collection? collection; - final dialog = createProgressDialog(context, "Creating album..."); - await dialog.show(); try { collection = await CollectionsService.instance.createAlbum(albumName); } catch (e, s) { _logger.severe(e, s); - await dialog.hide(); showGenericErrorDialog(context: context); - } finally { - await dialog.hide(); - } + } finally {} return collection; } Future _albumListItemOnTap(CollectionWithThumbnail item) async { - if (await _runCollectionAction( - item.collection.id, - )) { + if (await _runCollectionAction(collectionID: item.collection.id)) { showShortToast( context, widget.actionType == CollectionActionType.addFiles @@ -347,10 +316,16 @@ class _CreateCollectionSheetState extends State { ); } - Future _runCollectionAction(int collectionID) async { + Future _runCollectionAction({ + required int collectionID, + bool showProgressDialog = true, + }) async { switch (widget.actionType) { case CollectionActionType.addFiles: - return _addToCollection(collectionID); + return _addToCollection( + collectionID: collectionID, + showProgressDialog: showProgressDialog, + ); case CollectionActionType.moveFiles: return _moveFilesToCollection(collectionID); case CollectionActionType.unHide: @@ -360,14 +335,19 @@ class _CreateCollectionSheetState extends State { } } - Future _addToCollection(int collectionID) async { - final dialog = createProgressDialog( - context, - "Uploading files to album" - "...", - isDismissible: true, - ); - await dialog.show(); + Future _addToCollection({ + required int collectionID, + required bool showProgressDialog, + }) async { + final dialog = showProgressDialog + ? createProgressDialog( + context, + "Uploading files to album" + "...", + isDismissible: true, + ) + : null; + await dialog?.show(); try { final List files = []; final List filesPendingUpload = []; @@ -410,7 +390,7 @@ class _CreateCollectionSheetState extends State { CollectionsService.instance.getCollectionByID(collectionID); if (c != null && c.owner!.id != currentUserID) { showToast(context, "Can not upload to albums owned by others"); - await dialog.hide(); + await dialog?.hide(); return false; } else { // filesPendingUpload might be getting ignored during auto-upload @@ -424,12 +404,12 @@ class _CreateCollectionSheetState extends State { await CollectionsService.instance.addToCollection(collectionID, files); } RemoteSyncService.instance.sync(silently: true); - await dialog.hide(); + await dialog?.hide(); widget.selectedFiles?.clearAll(); return true; } catch (e, s) { _logger.severe("Could not add to album", e, s); - await dialog.hide(); + await dialog?.hide(); showGenericErrorDialog(context: context); } return false; diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index a065e4a6d..294a7a7c6 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -267,6 +267,8 @@ Future showTextInputDialog( String? initialValue, Alignment? alignMessage, int? maxLength, + bool showOnlyLoadingState = false, + TextCapitalization textCapitalization = TextCapitalization.none, }) { return showDialog( barrierColor: backdropFaintDark, @@ -290,6 +292,8 @@ Future showTextInputDialog( initialValue: initialValue, alignMessage: alignMessage, maxLength: maxLength, + showOnlyLoadingState: showOnlyLoadingState, + textCapitalization: textCapitalization, ), ), ); From bcadb168cb1811504257ffb85f6b0f62d663f5c0 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Thu, 9 Feb 2023 06:25:44 +0530 Subject: [PATCH 23/29] Now for dialogs using TextInputWidget, the dialog will return an exception when popped when one is thrown from TextInputWidget --- lib/ui/components/text_input_widget.dart | 22 +++++++++++++++------- lib/ui/create_collection_sheet.dart | 24 +++++++++++++++++------- lib/utils/dialog_util.dart | 2 +- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index fa4b67a27..63f29bf9f 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -51,6 +51,10 @@ class _TextInputWidgetState extends State { final _textController = TextEditingController(); final _debouncer = Debouncer(const Duration(milliseconds: 300)); + ///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() + Exception? _exception; + @override void initState() { widget.submitNotifier?.addListener(() { @@ -186,12 +190,16 @@ class _TextInputWidgetState extends State { }); }), ); - await widget.onSubmit - .call(_textController.text) - .onError((error, stackTrace) { + try { + await widget.onSubmit.call(_textController.text); + } catch (e) { executionState = ExecutionState.error; _debouncer.cancelDebounce(); - }); + _exception = e as Exception; + if (!widget.popNavAfterSubmission) { + rethrow; + } + } widget.alwaysShowSuccessState && _debouncer.isActive() ? executionState = ExecutionState.successful : null; @@ -243,7 +251,7 @@ class _TextInputWidgetState extends State { widget.popNavAfterSubmission ? Future.delayed( const Duration(seconds: 0), - () => _popNavigatorStack(context), + () => _popNavigatorStack(context, e: _exception), ) : null; }); @@ -258,8 +266,8 @@ class _TextInputWidgetState extends State { } } - void _popNavigatorStack(BuildContext context) { - Navigator.of(context).canPop() ? Navigator.of(context).pop() : null; + void _popNavigatorStack(BuildContext context, {Exception? e}) { + Navigator.of(context).canPop() ? Navigator.of(context).pop(e) : null; } } diff --git a/lib/ui/create_collection_sheet.dart b/lib/ui/create_collection_sheet.dart index c75ec1438..8effafe02 100644 --- a/lib/ui/create_collection_sheet.dart +++ b/lib/ui/create_collection_sheet.dart @@ -152,8 +152,9 @@ class _CreateCollectionSheetState extends State { if (index == 0 && widget.showOptionToCreateNewAlbum) { return GestureDetector( - onTap: () { - showTextInputDialog( + onTap: () async { + final result = + await showTextInputDialog( context, title: "Album title", submitButtonLabel: "OK", @@ -163,6 +164,15 @@ class _CreateCollectionSheetState extends State { textCapitalization: TextCapitalization.words, ); + if (result is Exception) { + showGenericErrorDialog( + context: context, + ); + _logger.severe( + "Failed to name album", + result, + ); + } }, behavior: HitTestBehavior.opaque, child: @@ -264,9 +274,9 @@ class _CreateCollectionSheetState extends State { try { collection = await CollectionsService.instance.createAlbum(albumName); } catch (e, s) { - _logger.severe(e, s); - showGenericErrorDialog(context: context); - } finally {} + _logger.severe("Failed to create album", e, s); + rethrow; + } return collection; } @@ -408,11 +418,11 @@ class _CreateCollectionSheetState extends State { widget.selectedFiles?.clearAll(); return true; } catch (e, s) { - _logger.severe("Could not add to album", e, s); + _logger.severe("Failed to add to album", e, s); await dialog?.hide(); showGenericErrorDialog(context: context); + rethrow; } - return false; } Future _moveFilesToCollection(int toCollectionID) async { diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 294a7a7c6..e63f2826a 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -253,7 +253,7 @@ Future showConfettiDialog({ ); } -Future showTextInputDialog( +Future showTextInputDialog( BuildContext context, { required String title, String? body, From 7bc4ded22ae47be3d65469097c5d2518ccdae11f Mon Sep 17 00:00:00 2001 From: ashilkn Date: Thu, 9 Feb 2023 06:43:54 +0530 Subject: [PATCH 24/29] Handled error and improved logging on failed rename album --- lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index f55336303..c7554ec0e 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -128,12 +128,13 @@ class _GalleryAppBarWidgetState extends State { _appBarTitle = text; setState(() {}); } - } catch (e) { + } catch (e, s) { + _logger.severe("Failed to rename album", e, s); rethrow; } }, ); - if (result == ButtonAction.error) { + if (result is Exception) { showGenericErrorDialog(context: context); } } From 4c430a6253705bb0178370955a7bd4c95c3036cf Mon Sep 17 00:00:00 2001 From: ashilkn Date: Thu, 9 Feb 2023 06:51:00 +0530 Subject: [PATCH 25/29] Show new error dialog on failing rename file --- lib/utils/magic_util.dart | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/utils/magic_util.dart b/lib/utils/magic_util.dart index 35c5ba5eb..4bb449817 100644 --- a/lib/utils/magic_util.dart +++ b/lib/utils/magic_util.dart @@ -100,7 +100,7 @@ Future editFilename( final fileName = file.displayName; final nameWithoutExt = basenameWithoutExtension(fileName); final extName = extension(fileName); - await showTextInputDialog( + final result = await showTextInputDialog( context, title: "Rename file", submitButtonLabel: "Rename", @@ -110,25 +110,24 @@ Future editFilename( hintText: "Enter file name", maxLength: 50, onSubmit: (String text) async { - try { - if (text.isEmpty || text.trim() == nameWithoutExt.trim()) { - return; - } - final newName = text + extName; - await _updatePublicMetadata( - context, - List.of([file]), - pubMagicKeyEditedName, - newName, - showProgressDialogs: false, - showDoneToast: false, - ); - } catch (e) { - showShortToast(context, 'Something went wrong'); - rethrow; + if (text.isEmpty || text.trim() == nameWithoutExt.trim()) { + return; } + final newName = text + extName; + await _updatePublicMetadata( + context, + List.of([file]), + pubMagicKeyEditedName, + newName, + showProgressDialogs: false, + showDoneToast: false, + ); }, ); + if (result is Exception) { + _logger.severe("Failed to rename file"); + showGenericErrorDialog(context: context); + } } Future editFileCaption( From 4485bcab07e31d699c1634c537a4ebae4203a6d2 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Thu, 9 Feb 2023 07:01:27 +0530 Subject: [PATCH 26/29] Dispose controllers & notifiers --- lib/ui/components/dialog_widget.dart | 7 +++++++ lib/ui/components/text_input_widget.dart | 1 + 2 files changed, 8 insertions(+) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index b12958bbd..533cca631 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -195,6 +195,13 @@ class TextInputDialog extends StatefulWidget { class _TextInputDialogState extends State { //the value of this ValueNotifier has no significance final _submitNotifier = ValueNotifier(false); + + @override + void dispose() { + _submitNotifier.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final widthOfScreen = MediaQuery.of(context).size.width; diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index 63f29bf9f..ceabfc38d 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -73,6 +73,7 @@ class _TextInputWidgetState extends State { @override void dispose() { widget.submitNotifier?.dispose(); + _textController.dispose(); super.dispose(); } From 7bbb53b16e965c4522176658bae431b410e91fc7 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Thu, 9 Feb 2023 07:19:43 +0530 Subject: [PATCH 27/29] Added comments + formatting --- lib/ui/components/text_input_widget.dart | 8 +++++++- lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/ui/components/text_input_widget.dart b/lib/ui/components/text_input_widget.dart index ceabfc38d..da91769f4 100644 --- a/lib/ui/components/text_input_widget.dart +++ b/lib/ui/components/text_input_widget.dart @@ -16,6 +16,9 @@ class TextInputWidget extends StatefulWidget { final Alignment? alignMessage; final bool? autoFocus; final int? maxLength; + + ///TextInputWidget will listen to this notifier and executes onSubmit when + ///notified. final ValueNotifier? submitNotifier; final bool alwaysShowSuccessState; final bool showOnlyLoadingState; @@ -90,7 +93,9 @@ class _TextInputWidgetState extends State { final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); var textInputChildren = []; - if (widget.label != null) textInputChildren.add(Text(widget.label!)); + if (widget.label != null) { + textInputChildren.add(Text(widget.label!)); + } textInputChildren.add( ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(8)), @@ -272,6 +277,7 @@ class _TextInputWidgetState extends State { } } +//todo: Add clear and custom icon for suffic icon class SuffixIconWidget extends StatelessWidget { final ExecutionState executionState; final bool shouldSurfaceExecutionStates; diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index c7554ec0e..31b2cc0b1 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -17,7 +17,6 @@ import 'package:photos/services/collections_service.dart'; import 'package:photos/services/sync_service.dart'; import 'package:photos/services/update_service.dart'; import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; -// import 'package:photos/ui/common/rename_dialog.dart'; import 'package:photos/ui/components/action_sheet_widget.dart'; import 'package:photos/ui/components/button_widget.dart'; import 'package:photos/ui/components/dialog_widget.dart'; From ef79387b9d076f78184c0615382aacac65a70ecf Mon Sep 17 00:00:00 2001 From: ashilkn Date: Thu, 9 Feb 2023 10:26:06 +0530 Subject: [PATCH 28/29] Always show success state on renaming album & file --- lib/ui/components/dialog_widget.dart | 5 ++++- lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 1 + lib/utils/dialog_util.dart | 2 ++ lib/utils/magic_util.dart | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/ui/components/dialog_widget.dart b/lib/ui/components/dialog_widget.dart index 533cca631..1818ae6c7 100644 --- a/lib/ui/components/dialog_widget.dart +++ b/lib/ui/components/dialog_widget.dart @@ -170,12 +170,12 @@ class TextInputDialog extends StatefulWidget { final int? maxLength; final bool showOnlyLoadingState; final TextCapitalization? textCapitalization; + final bool alwaysShowSuccessState; const TextInputDialog({ required this.title, this.body, required this.submitButtonLabel, required this.onSubmit, - required this.showOnlyLoadingState, this.icon, this.label, this.message, @@ -185,6 +185,8 @@ class TextInputDialog extends StatefulWidget { this.alignMessage, this.maxLength, this.textCapitalization, + this.showOnlyLoadingState = false, + this.alwaysShowSuccessState = false, super.key, }); @@ -243,6 +245,7 @@ class _TextInputDialogState extends State { popNavAfterSubmission: true, showOnlyLoadingState: widget.showOnlyLoadingState, textCapitalization: widget.textCapitalization, + alwaysShowSuccessState: widget.alwaysShowSuccessState, ), ), const SizedBox(height: 36), diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 31b2cc0b1..58859ad5c 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -115,6 +115,7 @@ class _GalleryAppBarWidgetState extends State { title: "Rename album", submitButtonLabel: "Rename", hintText: "Enter album name", + alwaysShowSuccessState: true, onSubmit: (String text) async { // indicates user cancelled the rename request if (text == "" || text.trim() == _appBarTitle!.trim()) { diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index e63f2826a..762d57305 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -269,6 +269,7 @@ Future showTextInputDialog( int? maxLength, bool showOnlyLoadingState = false, TextCapitalization textCapitalization = TextCapitalization.none, + bool alwaysShowSuccessState = false, }) { return showDialog( barrierColor: backdropFaintDark, @@ -294,6 +295,7 @@ Future showTextInputDialog( maxLength: maxLength, showOnlyLoadingState: showOnlyLoadingState, textCapitalization: textCapitalization, + alwaysShowSuccessState: alwaysShowSuccessState, ), ), ); diff --git a/lib/utils/magic_util.dart b/lib/utils/magic_util.dart index 4bb449817..fdd2e76b9 100644 --- a/lib/utils/magic_util.dart +++ b/lib/utils/magic_util.dart @@ -109,6 +109,7 @@ Future editFilename( alignMessage: Alignment.centerRight, hintText: "Enter file name", maxLength: 50, + alwaysShowSuccessState: true, onSubmit: (String text) async { if (text.isEmpty || text.trim() == nameWithoutExt.trim()) { return; From fa9d251e3268df4822e2a17dc96e099ad9360c21 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Thu, 9 Feb 2023 10:42:40 +0530 Subject: [PATCH 29/29] Capitalize album name on editing --- lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index 58859ad5c..515141d3e 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -116,6 +116,7 @@ class _GalleryAppBarWidgetState extends State { submitButtonLabel: "Rename", hintText: "Enter album name", alwaysShowSuccessState: true, + textCapitalization: TextCapitalization.words, onSubmit: (String text) async { // indicates user cancelled the rename request if (text == "" || text.trim() == _appBarTitle!.trim()) {