From 5e17cdee1a9bd1b78851ca9066c79d1fc1ced7c9 Mon Sep 17 00:00:00 2001 From: Freddy Cunningham Date: Mon, 17 Apr 2023 10:49:04 +0100 Subject: [PATCH 1/2] Create text prompt for empty screens [issue 306] --- lib/widgets/core/text_prompt.dart | 22 +++ lib/widgets/gallery/overview.dart | 137 +++++++-------- .../nutrition/nutritional_plans_list.dart | 157 +++++++++--------- lib/widgets/workouts/workout_plans_list.dart | 157 +++++++++--------- 4 files changed, 252 insertions(+), 221 deletions(-) create mode 100644 lib/widgets/core/text_prompt.dart diff --git a/lib/widgets/core/text_prompt.dart b/lib/widgets/core/text_prompt.dart new file mode 100644 index 000000000..92247ecc8 --- /dev/null +++ b/lib/widgets/core/text_prompt.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class TextPrompt extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Ready to start?', + style: Theme.of(context).textTheme.headlineMedium, + ), + const Padding( + padding: EdgeInsets.only(top: 12), + child: Text('Press the action button to begin'), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/gallery/overview.dart b/lib/widgets/gallery/overview.dart index e74d2a9df..e222ae0b8 100644 --- a/lib/widgets/gallery/overview.dart +++ b/lib/widgets/gallery/overview.dart @@ -24,6 +24,7 @@ import 'package:provider/provider.dart'; import 'package:wger/helpers/platform.dart'; import 'package:wger/providers/gallery.dart'; import 'package:wger/screens/form_screen.dart'; +import 'package:wger/widgets/core/text_prompt.dart'; import 'forms.dart'; @@ -38,77 +39,79 @@ class Gallery extends StatelessWidget { padding: const EdgeInsets.all(5), child: RefreshIndicator( onRefresh: () => provider.fetchAndSetGallery(), - child: MasonryGridView.count( - crossAxisCount: 2, - mainAxisSpacing: 5, - crossAxisSpacing: 5, - itemCount: provider.images.length, - itemBuilder: (context, index) { - final currentImage = provider.images[index]; + child: provider.images.length == 0 + ? TextPrompt() + : MasonryGridView.count( + crossAxisCount: 2, + mainAxisSpacing: 5, + crossAxisSpacing: 5, + itemCount: provider.images.length, + itemBuilder: (context, index) { + final currentImage = provider.images[index]; - return GestureDetector( - onTap: () { - showModalBottomSheet( - builder: (context) => Container( - key: Key('image-${currentImage.id}-detail'), - padding: const EdgeInsets.all(10), - color: Colors.white, - child: Column( - children: [ - Text( - DateFormat.yMd(Localizations.localeOf(context).languageCode) - .format(currentImage.date), - style: Theme.of(context).textTheme.headline5, - ), - Expanded( - child: Image.network(currentImage.url!), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Text(currentImage.description), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - Provider.of(context, listen: false) - .deleteImage(currentImage); - Navigator.of(context).pop(); - }), - if (!isDesktop) - IconButton( - icon: const Icon(Icons.edit), - onPressed: () { - Navigator.pushNamed( - context, - FormScreen.routeName, - arguments: FormScreenArguments( - AppLocalizations.of(context).edit, - ImageForm(currentImage), - hasListView: true, - ), - ); - }, + return GestureDetector( + onTap: () { + showModalBottomSheet( + builder: (context) => Container( + key: Key('image-${currentImage.id}-detail'), + padding: const EdgeInsets.all(10), + color: Colors.white, + child: Column( + children: [ + Text( + DateFormat.yMd(Localizations.localeOf(context).languageCode) + .format(currentImage.date), + style: Theme.of(context).textTheme.headline5, ), - ], - ) - ], + Expanded( + child: Image.network(currentImage.url!), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Text(currentImage.description), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + Provider.of(context, listen: false) + .deleteImage(currentImage); + Navigator.of(context).pop(); + }), + if (!isDesktop) + IconButton( + icon: const Icon(Icons.edit), + onPressed: () { + Navigator.pushNamed( + context, + FormScreen.routeName, + arguments: FormScreenArguments( + AppLocalizations.of(context).edit, + ImageForm(currentImage), + hasListView: true, + ), + ); + }, + ), + ], + ) + ], + ), + ), + context: context, + ); + }, + child: FadeInImage( + key: Key('image-${currentImage.id}'), + placeholder: const AssetImage('assets/images/placeholder.png'), + image: NetworkImage(currentImage.url!), + fit: BoxFit.cover, ), - ), - context: context, - ); - }, - child: FadeInImage( - key: Key('image-${currentImage.id}'), - placeholder: const AssetImage('assets/images/placeholder.png'), - image: NetworkImage(currentImage.url!), - fit: BoxFit.cover, + ); + }, ), - ); - }, - ), ), ); } diff --git a/lib/widgets/nutrition/nutritional_plans_list.dart b/lib/widgets/nutrition/nutritional_plans_list.dart index 673d5753f..c2678c206 100644 --- a/lib/widgets/nutrition/nutritional_plans_list.dart +++ b/lib/widgets/nutrition/nutritional_plans_list.dart @@ -21,6 +21,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/screens/nutritional_plan_screen.dart'; +import 'package:wger/widgets/core/text_prompt.dart'; class NutritionalPlansList extends StatelessWidget { final NutritionPlansProvider _nutritionProvider; @@ -31,87 +32,89 @@ class NutritionalPlansList extends StatelessWidget { Widget build(BuildContext context) { return RefreshIndicator( onRefresh: () => _nutritionProvider.fetchAndSetAllPlansSparse(), - child: ListView.builder( - padding: const EdgeInsets.all(10.0), - itemCount: _nutritionProvider.items.length, - itemBuilder: (context, index) { - final currentPlan = _nutritionProvider.items[index]; - return Dismissible( - key: Key(currentPlan.id.toString()), - confirmDismiss: (direction) async { - // Delete workout from DB - final bool? res = await showDialog( - context: context, - builder: (BuildContext contextDialog) { - return AlertDialog( - content: Text( - AppLocalizations.of(context).confirmDelete(currentPlan.description), - ), - actions: [ - TextButton( - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - onPressed: () => Navigator.of(contextDialog).pop(), - ), - TextButton( - child: Text( - AppLocalizations.of(context).delete, - style: TextStyle(color: Theme.of(context).errorColor), - ), - onPressed: () { - // Confirmed, delete the workout - _nutritionProvider.deletePlan(currentPlan.id!); + child: _nutritionProvider.items.length == 0 + ? TextPrompt() + : ListView.builder( + padding: const EdgeInsets.all(10.0), + itemCount: _nutritionProvider.items.length, + itemBuilder: (context, index) { + final currentPlan = _nutritionProvider.items[index]; + return Dismissible( + key: Key(currentPlan.id.toString()), + confirmDismiss: (direction) async { + // Delete workout from DB + final bool? res = await showDialog( + context: context, + builder: (BuildContext contextDialog) { + return AlertDialog( + content: Text( + AppLocalizations.of(context).confirmDelete(currentPlan.description), + ), + actions: [ + TextButton( + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + onPressed: () => Navigator.of(contextDialog).pop(), + ), + TextButton( + child: Text( + AppLocalizations.of(context).delete, + style: TextStyle(color: Theme.of(context).errorColor), + ), + onPressed: () { + // Confirmed, delete the workout + _nutritionProvider.deletePlan(currentPlan.id!); - // Close the popup - Navigator.of(contextDialog).pop(); + // Close the popup + Navigator.of(contextDialog).pop(); - // and inform the user - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context).successfullyDeleted, - textAlign: TextAlign.center, - ), + // and inform the user + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context).successfullyDeleted, + textAlign: TextAlign.center, + ), + ), + ); + }, ), - ); - }, - ), - ], - ); - }); - return res; - }, - background: Container( - color: Theme.of(context).errorColor, - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 20), - margin: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 4, - ), - child: const Icon( - Icons.delete, - color: Colors.white, - ), - ), - direction: DismissDirection.endToStart, - child: Card( - child: ListTile( - onTap: () { - Navigator.of(context).pushNamed( - NutritionalPlanScreen.routeName, - arguments: currentPlan, - ); - }, - title: Text(currentPlan.getLabel(context)), - subtitle: Text( - DateFormat.yMd(Localizations.localeOf(context).languageCode) - .format(currentPlan.creationDate), - ), - ), + ], + ); + }); + return res; + }, + background: Container( + color: Theme.of(context).errorColor, + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 20), + margin: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 4, + ), + child: const Icon( + Icons.delete, + color: Colors.white, + ), + ), + direction: DismissDirection.endToStart, + child: Card( + child: ListTile( + onTap: () { + Navigator.of(context).pushNamed( + NutritionalPlanScreen.routeName, + arguments: currentPlan, + ); + }, + title: Text(currentPlan.getLabel(context)), + subtitle: Text( + DateFormat.yMd(Localizations.localeOf(context).languageCode) + .format(currentPlan.creationDate), + ), + ), + ), + ); + }, ), - ); - }, - ), ); } } diff --git a/lib/widgets/workouts/workout_plans_list.dart b/lib/widgets/workouts/workout_plans_list.dart index c0064da41..875bf5d7f 100644 --- a/lib/widgets/workouts/workout_plans_list.dart +++ b/lib/widgets/workouts/workout_plans_list.dart @@ -22,6 +22,7 @@ import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:wger/providers/workout_plans.dart'; import 'package:wger/screens/workout_plan_screen.dart'; +import 'package:wger/widgets/core/text_prompt.dart'; class WorkoutPlansList extends StatelessWidget { final WorkoutPlansProvider _workoutProvider; @@ -32,88 +33,90 @@ class WorkoutPlansList extends StatelessWidget { Widget build(BuildContext context) { return RefreshIndicator( onRefresh: () => _workoutProvider.fetchAndSetAllPlansSparse(), - child: ListView.builder( - padding: const EdgeInsets.all(10.0), - itemCount: _workoutProvider.items.length, - itemBuilder: (context, index) { - final currentWorkout = _workoutProvider.items[index]; - return Dismissible( - key: Key(currentWorkout.id.toString()), - confirmDismiss: (direction) async { - // Delete workout from DB - final res = await showDialog( - context: context, - builder: (BuildContext contextDialog) { - return AlertDialog( - content: Text( - AppLocalizations.of(context).confirmDelete(currentWorkout.name), - ), - actions: [ - TextButton( - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - onPressed: () => Navigator.of(contextDialog).pop(), - ), - TextButton( - child: Text( - AppLocalizations.of(context).delete, - style: TextStyle(color: Theme.of(context).errorColor), - ), - onPressed: () { - // Confirmed, delete the workout - Provider.of(context, listen: false) - .deleteWorkout(currentWorkout.id!); + child: _workoutProvider.items.length == 0 + ? TextPrompt() + : ListView.builder( + padding: const EdgeInsets.all(10.0), + itemCount: _workoutProvider.items.length, + itemBuilder: (context, index) { + final currentWorkout = _workoutProvider.items[index]; + return Dismissible( + key: Key(currentWorkout.id.toString()), + confirmDismiss: (direction) async { + // Delete workout from DB + final res = await showDialog( + context: context, + builder: (BuildContext contextDialog) { + return AlertDialog( + content: Text( + AppLocalizations.of(context).confirmDelete(currentWorkout.name), + ), + actions: [ + TextButton( + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + onPressed: () => Navigator.of(contextDialog).pop(), + ), + TextButton( + child: Text( + AppLocalizations.of(context).delete, + style: TextStyle(color: Theme.of(context).errorColor), + ), + onPressed: () { + // Confirmed, delete the workout + Provider.of(context, listen: false) + .deleteWorkout(currentWorkout.id!); - // Close the popup - Navigator.of(contextDialog).pop(); + // Close the popup + Navigator.of(contextDialog).pop(); - // and inform the user - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context).successfullyDeleted, - textAlign: TextAlign.center, - ), + // and inform the user + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context).successfullyDeleted, + textAlign: TextAlign.center, + ), + ), + ); + }, ), - ); - }, - ), - ], - ); - }); - return res; - }, - background: Container( - color: Theme.of(context).errorColor, - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 20), - margin: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 4, - ), - child: const Icon( - Icons.delete, - color: Colors.white, - ), - ), - direction: DismissDirection.endToStart, - child: Card( - child: ListTile( - onTap: () { - _workoutProvider.setCurrentPlan(currentWorkout.id!); + ], + ); + }); + return res; + }, + background: Container( + color: Theme.of(context).errorColor, + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 20), + margin: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 4, + ), + child: const Icon( + Icons.delete, + color: Colors.white, + ), + ), + direction: DismissDirection.endToStart, + child: Card( + child: ListTile( + onTap: () { + _workoutProvider.setCurrentPlan(currentWorkout.id!); - Navigator.of(context) - .pushNamed(WorkoutPlanScreen.routeName, arguments: currentWorkout); - }, - title: Text(currentWorkout.name), - subtitle: Text( - DateFormat.yMd(Localizations.localeOf(context).languageCode) - .format(currentWorkout.creationDate), - ), - ), + Navigator.of(context) + .pushNamed(WorkoutPlanScreen.routeName, arguments: currentWorkout); + }, + title: Text(currentWorkout.name), + subtitle: Text( + DateFormat.yMd(Localizations.localeOf(context).languageCode) + .format(currentWorkout.creationDate), + ), + ), + ), + ); + }, ), - ); - }, - ), ); } } From 043fa29d6fb9e00c5b6218525e7cdf2130642dc2 Mon Sep 17 00:00:00 2001 From: Freddy Cunningham Date: Tue, 18 Apr 2023 12:51:23 +0100 Subject: [PATCH 2/2] Text prompt strings marked as translatable --- lib/l10n/app_en.arb | 4 +++- lib/widgets/core/text_prompt.dart | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cdf70d3d1..f0fd2187c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -665,5 +665,7 @@ "swiss_ball": "Swiss Ball", "triceps": "Triceps", "until_failure": "Until Failure", - "none__bodyweight_exercise_": "none (bodyweight exercise)" + "none__bodyweight_exercise_": "none (bodyweight exercise)", + "textPromptTitle": "Ready to start?", + "textPromptSubheading": "Press the action button to begin" } diff --git a/lib/widgets/core/text_prompt.dart b/lib/widgets/core/text_prompt.dart index 92247ecc8..c2b14e775 100644 --- a/lib/widgets/core/text_prompt.dart +++ b/lib/widgets/core/text_prompt.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class TextPrompt extends StatelessWidget { @override @@ -8,12 +9,12 @@ class TextPrompt extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'Ready to start?', + AppLocalizations.of(context).textPromptTitle, style: Theme.of(context).textTheme.headlineMedium, ), - const Padding( + Padding( padding: EdgeInsets.only(top: 12), - child: Text('Press the action button to begin'), + child: Text(AppLocalizations.of(context).textPromptSubheading), ), ], ),